diff --git a/bin/myapp-0.1-arm64-v8a_armeabi-v7a-debug.apk b/bin/uptimecheck-0.1-arm64-v8a_armeabi-v7a-debug.apk similarity index 86% rename from bin/myapp-0.1-arm64-v8a_armeabi-v7a-debug.apk rename to bin/uptimecheck-0.1-arm64-v8a_armeabi-v7a-debug.apk index f22e05f..5bfe68b 100644 Binary files a/bin/myapp-0.1-arm64-v8a_armeabi-v7a-debug.apk and b/bin/uptimecheck-0.1-arm64-v8a_armeabi-v7a-debug.apk differ diff --git a/buildozer.spec b/buildozer.spec index 9fcff8c..18b5de7 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -1,13 +1,13 @@ [app] # (str) Title of your application -title = My Application +title = Uptime Check # (str) Package name -package.name = myapp +package.name = uptimecheck # (str) Package domain (needed for android/ios packaging) -package.domain = org.test +package.domain = local.meko # (str) Source code where the main.py live source.dir = . @@ -37,7 +37,7 @@ version = 0.1 # (list) Application requirements # comma separated e.g. requirements = sqlite3,kivy -requirements = python3,kivy +requirements = python3,kivy,requests # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes diff --git a/kivy_venv/lib/python3.11/site-packages/__pycache__/asyncgui.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/__pycache__/asyncgui.cpython-311.pyc new file mode 100644 index 0000000..600c5a4 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/__pycache__/asyncgui.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/INSTALLER b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/INSTALLER similarity index 100% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/INSTALLER rename to kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/INSTALLER diff --git a/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/LICENSE b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/LICENSE new file mode 100644 index 0000000..ffa4a40 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/LICENSE @@ -0,0 +1,7 @@ +Copyright 2020 Nattōsai Mitō + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/METADATA b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/METADATA new file mode 100644 index 0000000..827a183 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/METADATA @@ -0,0 +1,57 @@ +Metadata-Version: 2.1 +Name: asyncgui +Version: 0.6.3 +Summary: A thin layer that helps to wrap a callback-style API in an async/await-style API +Home-page: https://github.com/gottadiveintopython/asyncgui +License: MIT +Keywords: async +Author: Nattōsai Mitō +Author-email: flow4re2c@gmail.com +Requires-Python: >=3.8.1,<4.0.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development :: Libraries +Requires-Dist: exceptiongroup (>=1.0.4,<2.0.0) ; python_version < "3.11" +Project-URL: Repository, https://github.com/gottadiveintopython/asyncgui +Description-Content-Type: text/markdown + +# AsyncGui + +A thin layer that helps to wrap a callback-style API in an async/await-style API. + +An async library that focuses on fast reaction. + +[Documentation](https://asyncgui.github.io/asyncgui/) + +## Installation + +Pin the minor version. + +```text +poetry add asyncgui@~0.6 +pip install "asyncgui>=0.6,<0.7" +``` + +## Tested on + +- CPython 3.8 +- CPython 3.9 +- CPython 3.10 +- CPython 3.11 +- CPython 3.12 (3.12.1 or later) + +## Async libraries that rely on this + +- [asynckivy](https://github.com/asyncgui/asynckivy) +- [asynctkinter](https://github.com/asyncgui/asynctkinter) +- [asyncpygame](https://github.com/asyncgui/asyncpygame) + diff --git a/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/RECORD b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/RECORD new file mode 100644 index 0000000..5dc9ca0 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/RECORD @@ -0,0 +1,7 @@ +__pycache__/asyncgui.cpython-311.pyc,, +asyncgui-0.6.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +asyncgui-0.6.3.dist-info/LICENSE,sha256=zdEfMrLu9-uJk1Y-D7fn_gqgODq71StrNs47LvNE5b0,1055 +asyncgui-0.6.3.dist-info/METADATA,sha256=OTiDgXiKJm39p7OP5DZQ6ZtiY6UU2tawUD1QfFIvbFs,1729 +asyncgui-0.6.3.dist-info/RECORD,, +asyncgui-0.6.3.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88 +asyncgui.py,sha256=89sbdDMVyRooyYsy7IqG7cLm8DJVY5U67K1kYQngBGE,27963 diff --git a/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/WHEEL b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/WHEEL new file mode 100644 index 0000000..258a6ff --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asyncgui-0.6.3.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: poetry-core 1.6.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/kivy_venv/lib/python3.11/site-packages/asyncgui.py b/kivy_venv/lib/python3.11/site-packages/asyncgui.py new file mode 100644 index 0000000..da09f01 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asyncgui.py @@ -0,0 +1,945 @@ +__all__ = ( + # core (exceptions) + 'ExceptionGroup', 'BaseExceptionGroup', 'InvalidStateError', 'Cancelled', + + # core + 'Aw_or_Task', 'start', 'Task', 'TaskState', 'disable_cancellation', 'open_cancel_scope', 'CancelScope', + 'dummy_task', 'current_task', '_current_task', 'sleep_forever', '_sleep_forever', + + # structured concurrency + 'wait_all', 'wait_any', 'and_', 'or_', 'wait_all_cm', 'wait_any_cm', 'move_on_when', + 'run_as_main', 'run_as_daemon', + 'open_nursery', 'Nursery', + + # bridge between async-world and sync-world + 'AsyncBox', 'AsyncEvent', + + # deprecated + 'Event', 'run_as_primary', 'run_as_secondary', +) +import types +import typing as T +from inspect import getcoroutinestate, CORO_CREATED, CORO_SUSPENDED, isawaitable +import sys +import itertools +from functools import cached_property, partial +import enum +from contextlib import asynccontextmanager + +# ----------------------------------------------------------------------------- +# Core +# ----------------------------------------------------------------------------- + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup, ExceptionGroup +else: + BaseExceptionGroup = BaseExceptionGroup #: :meta private: + ExceptionGroup = ExceptionGroup #: :meta private: + +potential_bug_msg = \ + r"This may be a bug of this library. Please make a minimal code that reproduces the bug, and open an issue at " \ + r"the GitHub repository, then post the code there. (https://github.com/asyncgui/asyncgui)." + + +class InvalidStateError(Exception): + """The operation is not allowed in the current state.""" + + +class _Cancelled(BaseException): + @cached_property + def level(self) -> int: + return self.args[0] + + +Cancelled = (_Cancelled, GeneratorExit, ) +''' +Exception class that represents cancellation. +See :ref:`dealing-with-cancellation`. + +.. warning:: + + Actually, this is not an exception class but a tuple of exception classes for now. + But that's an implementation detail, and it might become an actual class in the future; + therefore, your code must be compatible in both cases. + +:meta hide-value: +''' + + +class TaskState(enum.Enum): + ''' + Enum class that represents the Task state. + ''' + + CREATED = enum.auto() + ''' + Waiting to start execution. + + :meta hide-value: + ''' + + STARTED = enum.auto() + ''' + Currently running or suspended. + + :meta hide-value: + ''' + + CANCELLED = enum.auto() + ''' + The execution has been cancelled. + The cause of cancellation can be either :meth:`Task.cancel` or an unhandled exception. + + :meta hide-value: + ''' + + FINISHED = enum.auto() + ''' + The execution has been completed. + + :meta hide-value: + ''' + + +_next_Task_uid = itertools.count().__next__ + + +class Task: + __slots__ = ( + '_uid', '_root_coro', '_state', '_result', '_on_end', + '_exc_caught', '_suppresses_exc', + '_cancel_disabled', '_cancel_depth', '_cancel_level', + ) + + def __init__(self, aw: T.Awaitable, /): + if not isawaitable(aw): + raise ValueError(str(aw) + " is not awaitable.") + self._uid = _next_Task_uid() + self._cancel_disabled = 0 + self._root_coro = self._wrapper(aw) + self._state = TaskState.CREATED + self._on_end = None + self._cancel_depth = 0 + self._cancel_level = None + self._exc_caught = None + self._suppresses_exc = False + + def __str__(self): + return f'Task(state={self._state.name}, uid={self._uid})' + + @property + def uid(self) -> int: + ''' + An unique integer assigned to the task. + This exists solely for inspection purposes. + ''' + return self._uid + + @property + def root_coro(self) -> T.Coroutine: + ''' + The starting point of the coroutine chain for the task. + This exists solely for inspection purposes. + ''' + return self._root_coro + + @property + def state(self) -> TaskState: + ''' + The current state of the task. + This exists solely for inspection purposes. + ''' + return self._state + + @property + def finished(self) -> bool: + '''Whether the task has been completed.''' + return self._state is TaskState.FINISHED + + @property + def cancelled(self) -> bool: + '''Whether the task has been cancelled.''' + return self._state is TaskState.CANCELLED + + @property + def result(self) -> T.Any: + '''Result of the task. If the task is not finished, :exc:`InvalidStateError` will be raised. ''' + state = self._state + if state is TaskState.FINISHED: + return self._result + elif state is TaskState.CANCELLED: + raise InvalidStateError(f"{self} was cancelled") + else: + raise InvalidStateError(f"Result of {self} is not ready") + + async def _wrapper(self, aw, /): + try: + self._state = TaskState.STARTED + self._result = await aw + except _Cancelled as e: + self._state = TaskState.CANCELLED + assert e.level == 0, potential_bug_msg + assert self._cancel_level == 0, potential_bug_msg + except Exception as e: + self._state = TaskState.CANCELLED + self._exc_caught = e + if not self._suppresses_exc: + raise + except: # noqa: E722 + self._state = TaskState.CANCELLED + raise + else: + self._state = TaskState.FINISHED + finally: + assert self._cancel_depth == 0, potential_bug_msg + if (on_end := self._on_end) is not None: + on_end(self) + + def cancel(self, _level=0, /): + '''Cancel the task as soon as possible.''' + if self._cancel_level is None: + self._cancel_level = _level + state = getcoroutinestate(self._root_coro) + if state is CORO_SUSPENDED: + if not self._cancel_disabled: + self._actual_cancel() + elif state is CORO_CREATED: + self._root_coro.close() + self._state = TaskState.CANCELLED + else: + self._cancel_level = min(self._cancel_level, _level) + + def _actual_cancel(self): + try: + self._root_coro.throw(_Cancelled(self._cancel_level))(self) + except StopIteration: + pass + else: + self._cancel_if_needed() + + close = cancel + '''An alias for :meth:`cancel`.''' + + @property + def _cancel_requested(self) -> bool: + return self._cancel_level is not None + + @property + def _is_cancellable(self) -> bool: + '''Whether the task can be cancelled immediately.''' + return (not self._cancel_disabled) and getcoroutinestate(self._root_coro) is CORO_SUSPENDED + + def _cancel_if_needed(self, getcoroutinestate=getcoroutinestate, CORO_SUSPENDED=CORO_SUSPENDED): + if (self._cancel_level is None) or self._cancel_disabled or \ + (getcoroutinestate(self._root_coro) is not CORO_SUSPENDED): + pass + else: + self._actual_cancel() + + def _step(self, *args, **kwargs): + coro = self._root_coro + if getcoroutinestate(coro) is not CORO_SUSPENDED: + return + try: + coro.send((args, kwargs, ))(self) + except StopIteration: + pass + else: + self._cancel_if_needed() + + def _throw_exc(self, exc): + '''停止中のTaskへ例外を投げる。Taskが停止中ではない場合は :exc:`InvalidStateError` が起こる。''' + coro = self._root_coro + if getcoroutinestate(coro) is not CORO_SUSPENDED: + raise InvalidStateError("Throwing an exception to an unstarted/running/closed task is not allowed.") + try: + coro.throw(exc)(self) + except StopIteration: + pass + else: + self._cancel_if_needed() + + +Aw_or_Task = T.Union[T.Awaitable, Task] + + +def start(aw: Aw_or_Task, /) -> Task: + '''*Immediately* start a Task/Awaitable. + + If the argument is a :class:`Task`, itself will be returned. If it's an :class:`typing.Awaitable`, + it will be wrapped in a Task, and that Task will be returned. + + .. code-block:: + + async def async_func(): + ... + + task = start(async_func()) + ''' + if isawaitable(aw): + task = Task(aw) + elif isinstance(aw, Task): + task = aw + if task._state is not TaskState.CREATED: + raise ValueError(f"{task} has already started") + else: + raise ValueError("Argument must be either a Task or an awaitable.") + + try: + task._root_coro.send(None)(task) + except StopIteration: + pass + else: + task._cancel_if_needed() + + return task + + +class CancelScope: + ''' + An equivalence of :class:`trio.CancelScope`. + You should not directly instantiate this, use :func:`open_cancel_scope`. + ''' + __slots__ = ('_task', '_level', 'cancelled_caught', 'cancel_called', ) + + def __init__(self, task: Task, /): + self._task = task + self.cancelled_caught = False #: Whether the scope caught a corresponding :class:`Cancelled` instance. + self.cancel_called = False #: Whether the :meth:`cancel` has been called. + + def __enter__(self) -> 'CancelScope': + t = self._task + t._cancel_depth = self._level = t._cancel_depth + 1 + return self + + def __exit__(self, exc_type, exc, __): + # LOAD_FAST + task = self._task + level = task._cancel_level + scope_level = self._level + + self._task = None + task._cancel_depth -= 1 + if level is not None: + if level == scope_level: + task._cancel_level = None + else: + assert level < scope_level, potential_bug_msg + if exc_type is not _Cancelled: + return + level = exc.level + if level == scope_level: + self.cancelled_caught = True + return True + else: + assert level < scope_level, potential_bug_msg + + @property + def closed(self) -> bool: + ''' + Whether this scope has been closed. + The cause of the closure of the scope can be either an exception occurred or the scope exited gracefully, + ''' + return self._task is None + + def cancel(self): + '''Cancel the execution inside this scope as soon as possible. ''' + if self.cancel_called: + return + self.cancel_called = True + if not self.closed: + self._task.cancel(self._level) + + +class open_cancel_scope: + ''' + Same as :class:`trio.CancelScope` except this one returns an async context manager. + + .. code-block:: + + async with open_cancel_scope() as scope: + ... + ''' + __slots__ = ('_scope', ) + + async def __aenter__(self) -> T.Awaitable[CancelScope]: + self._scope = CancelScope(await current_task()) + return self._scope.__enter__() + + async def __aexit__(self, *args): + return self._scope.__exit__(*args) + + +def _current_task(task): + return task._step(task) + + +@types.coroutine +def current_task(_f=_current_task) -> T.Awaitable[Task]: + '''Returns the Task instance corresponding to the caller. + + .. code-block:: + + task = await current_task() + ''' + return (yield _f)[0][0] + + +class disable_cancellation: + ''' + Return an async context manager that protects its code-block from cancellation. + + .. code-block:: + + async with disable_cancellation(): + await something # <- never gets cancelled + ''' + + __slots__ = ('_task', ) + + async def __aenter__(self): + self._task = task = await current_task() + task._cancel_disabled += 1 + + async def __aexit__(self, *__): + self._task._cancel_disabled -= 1 + + +def _sleep_forever(task): + pass + + +@types.coroutine +def sleep_forever(_f=_sleep_forever) -> T.Awaitable: + ''' + .. code-block:: + + await sleep_forever() + ''' + yield _f + + +dummy_task = Task(sleep_forever()) +''' +An already closed task. +This can be utilized to prevent the need for the common null validation mentioned below. + +*Before:* + +.. code-block:: + :emphasize-lines: 3, 6,7 + + class MyClass: + def __init__(self): + self._task = None + + def restart(self): + if self._task is not None: + self._task.cancel() + self._task = asyncgui.start(self.main()) + + async def main(self): + ... + +*After:* + +.. code-block:: + :emphasize-lines: 3, 6 + + class MyClass: + def __init__(self): + self._task = asyncgui.dummy_task + + def restart(self): + self._task.cancel() + self._task = asyncgui.start(self.main()) + + async def main(self): + ... +''' +dummy_task.cancel() + +# ----------------------------------------------------------------------------- +# Bridge between async-world and sync-world +# ----------------------------------------------------------------------------- + + +class AsyncEvent: + ''' + .. code-block:: + + async def async_fn(e): + args, kwargs = await e.wait() + assert args == (2, ) + assert kwargs == {'crow': 'raven', } + + args, kwargs = await e.wait() + assert args == (3, ) + assert kwargs == {'toad': 'frog', } + + e = AsyncEvent() + e.fire(1, crocodile='alligator') + start(async_fn(e)) + e.fire(2, crow='raven') + e.fire(3, toad='frog') + + .. warning:: + + This class is not designed for inter-task synchronization, unlike :class:`asyncio.Event`. + When multiple tasks simultaneously try to wait for the same event to fire, it will raise an exception. + ''' + __slots__ = ('_callback', ) + + def __init__(self): + self._callback = None + + def fire(self, *args, **kwargs): + if (f := self._callback) is not None: + f(*args, **kwargs) + + @types.coroutine + def wait(self): + if self._callback is not None: + raise InvalidStateError("There's already a task waiting for the event to fire.") + try: + return (yield self._attach_task) + finally: + self._callback = None + + def _attach_task(self, task): + self._callback = task._step + + +class AsyncBox: + ''' + .. code-block:: + + async def async_fn(b1, b2): + args, kwargs = await b1.get() + assert args == (1, ) + assert kwargs == {'crow': 'raven', } + + args, kwargs = await b2.get() + assert args == (2, ) + assert kwargs == {'frog': 'toad', } + + args, kwargs = await b1.get() + assert args == (1, ) + assert kwargs == {'crow': 'raven', } + + b1 = AsyncBox() + b2 = AsyncBox() + b1.put(1, crow='raven') + start(async_fn(b1, b2)) + b2.put(2, frog='toad') + + .. warning:: + + This class is not designed for inter-task synchronization, unlike :class:`asyncio.Event`. + When multiple tasks simultaneously try to get an item from the same box, it will raise an exception. + ''' + __slots__ = ('_item', '_callback', ) + + def __init__(self): + self._item = None + self._callback = None + + @property + def is_empty(self) -> bool: + '''Whether the box is empty.''' + return self._item is None + + def put(self, *args, **kwargs): + '''Put an item into the box if it's empty.''' + if self._item is None: + self.put_or_update(*args, **kwargs) + + def update(self, *args, **kwargs): + '''Replace the item in the box if there is one already.''' + if self._item is not None: + self.put_or_update(*args, **kwargs) + + def put_or_update(self, *args, **kwargs): + self._item = (args, kwargs, ) + if (callback := self._callback) is not None: + callback(*args, **kwargs) + + @types.coroutine + def get(self): + '''Get the item from the box if there is one. Otherwise, wait until it's put.''' + if self._callback is not None: + raise InvalidStateError("There's already a task waiting for an item to be put in the box.") + if self._item is None: + try: + return (yield self._attach_task) + finally: + self._callback = None + else: + return self._item + + def clear(self): + '''Remove the item from the box if there is one.''' + self._item = None + + _attach_task = AsyncEvent._attach_task + + +class Event: + ''' + Similar to :class:`asyncio.Event`. + The differences are: + + * :meth:`set` accepts any number of arguments but doesn't use them at all so it can be used as a callback function + in any library. + * :attr:`is_set` is a property not a function. + + .. code-block:: + + e = Event() + any_library.register_callback(e.set) + + .. deprecated:: 0.6.2 + + This class is deprecated, and will be removed before 1.0.0. + Use :class:`asyncgui_ext.synctools.event.Event` instead. + ''' + + __slots__ = ('_flag', '_waiting_tasks', ) + + def __init__(self): + self._flag = False + self._waiting_tasks = [] + + @property + def is_set(self) -> bool: + return self._flag + + def set(self, *args, **kwargs): + ''' + Set the event. + Unlike asyncio's, all tasks waiting for this event to be set will be resumed *immediately*. + ''' + if self._flag: + return + self._flag = True + tasks = self._waiting_tasks + self._waiting_tasks = [] + for t in tasks: + if t is not None: + t._step() + + def clear(self): + '''Unset the event.''' + self._flag = False + + @types.coroutine + def wait(self) -> T.Awaitable: + ''' + Wait for the event to be set. + Return *immediately* if it's already set. + ''' + if self._flag: + return + try: + tasks = self._waiting_tasks + idx = len(tasks) + yield tasks.append + finally: + tasks[idx] = None + + +# ----------------------------------------------------------------------------- +# Structured concurrency +# ----------------------------------------------------------------------------- + + +class TaskCounter: + ''' + (internal) + 数値が零になった事を通知する仕組みを持つカウンター。 + 親taskが自分の子task達の終了を待つのに用いる。 + ''' + + __slots__ = ('_box', '_n_tasks', ) + + def __init__(self, initial=0, /): + self._n_tasks = initial + self._box = AsyncBox() + + def increase(self): + self._n_tasks += 1 + + def decrease(self): + n = self._n_tasks - 1 + assert n >= 0, potential_bug_msg + self._n_tasks = n + if not n: + self._box.put() + + async def to_be_zero(self) -> T.Awaitable: + if self._n_tasks: + box = self._box + box._item = None + await box.get() + + def __bool__(self): + return not not self._n_tasks # 'not not' is not a typo + + +async def _wait_xxx(debug_msg, on_child_end, *aws: T.Iterable[Aw_or_Task]) -> T.Awaitable[T.Sequence[Task]]: + children = tuple(v if isinstance(v, Task) else Task(v) for v in aws) + if not children: + return children + counter = TaskCounter(len(children)) + parent = await current_task() + + try: + with CancelScope(parent) as scope: + on_child_end = partial(on_child_end, scope, counter) + for c in children: + c._suppresses_exc = True + c._on_end = on_child_end + start(c) + await counter.to_be_zero() + finally: + if counter: + for c in children: + c.cancel() + if counter: + try: + parent._cancel_disabled += 1 + await counter.to_be_zero() + finally: + parent._cancel_disabled -= 1 + exceptions = tuple(e for c in children if (e := c._exc_caught) is not None) + if exceptions: + raise ExceptionGroup(debug_msg, exceptions) + if (parent._cancel_level is not None) and (not parent._cancel_disabled): + await sleep_forever() + assert False, potential_bug_msg + return children + + +def _on_child_end__ver_all(scope, counter, child): + counter.decrease() + if child._exc_caught is not None: + scope.cancel() + + +def _on_child_end__ver_any(scope, counter, child): + counter.decrease() + if child._exc_caught is not None or child.finished: + scope.cancel() + + +_wait_xxx_type = T.Callable[..., T.Awaitable[T.Sequence[Task]]] +wait_all: _wait_xxx_type = partial(_wait_xxx, "wait_all()", _on_child_end__ver_all) +''' +Run multiple tasks concurrently, and wait for **all** of them to **end**. When any of them raises an exception, the +others will be cancelled, and the exception will be propagated to the caller, like :class:`trio.Nursery`. + +.. code-block:: + + tasks = await wait_all(async_fn1(), async_fn2(), async_fn3()) + if tasks[0].finished: + print("The return value of async_fn1() :", tasks[0].result) +''' +wait_any: _wait_xxx_type = partial(_wait_xxx, "wait_any()", _on_child_end__ver_any) +''' +Run multiple tasks concurrently, and wait for **any** of them to **finish**. As soon as that happens, the others will be +cancelled. When any of them raises an exception, the others will be cancelled, and the exception will be propagated to +the caller, like :class:`trio.Nursery`. + +.. code-block:: + + tasks = await wait_any(async_fn1(), async_fn2(), async_fn3()) + if tasks[0].finished: + print("The return value of async_fn1() :", tasks[0].result) +''' + + +@asynccontextmanager +async def _wait_xxx_cm(debug_msg, on_child_end, wait_bg, aw: Aw_or_Task): + counter = TaskCounter(1) + fg_task = await current_task() + bg_task = aw if isinstance(aw, Task) else Task(aw) + exc = None + + try: + with CancelScope(fg_task) as scope: + bg_task._on_end = partial(on_child_end, scope, counter) + bg_task._suppresses_exc = True + yield start(bg_task) + if wait_bg: + await counter.to_be_zero() + except Exception as e: + exc = e + finally: + bg_task.cancel() + if counter: + try: + fg_task._cancel_disabled += 1 + await counter.to_be_zero() + finally: + fg_task._cancel_disabled -= 1 + excs = tuple( + e for e in (exc, bg_task._exc_caught, ) + if e is not None + ) + if excs: + raise ExceptionGroup(debug_msg, excs) + if (fg_task._cancel_level is not None) and (not fg_task._cancel_disabled): + await sleep_forever() + assert False, potential_bug_msg + + +_wait_xxx_cm_type = T.Callable[[Aw_or_Task], T.AsyncContextManager[Task]] +wait_all_cm: _wait_xxx_cm_type = partial(_wait_xxx_cm, "wait_all_cm()", _on_child_end__ver_all, True) +''' +The context manager form of :func:`wait_all`. + +.. code-block:: + + async with wait_all_cm(async_fn()) as bg_task: + ... +''' +wait_any_cm: _wait_xxx_cm_type = partial(_wait_xxx_cm, "wait_any_cm()", _on_child_end__ver_any, False) +''' +The context manager form of :func:`wait_any`, an equivalence of :func:`trio_util.move_on_when`. + +.. code-block:: + + async with wait_any_cm(async_fn()) as bg_task: + ... +''' +run_as_main: _wait_xxx_cm_type = partial(_wait_xxx_cm, "run_as_main()", _on_child_end__ver_any, True) +''' +.. code-block:: + + async with run_as_main(async_fn()) as task: + ... + +.. note:: + + You need to use its older name, ``run_as_primary``, if you are using ``asyncgui`` 0.6.2 or older. +''' +run_as_daemon: _wait_xxx_cm_type = partial(_wait_xxx_cm, "run_as_daemon()", _on_child_end__ver_all, False) +''' +.. code-block:: + + async with run_as_daemon(async_fn()) as bg_task: + ... +''' + + +class Nursery: + ''' + Similar to :class:`trio.Nursery`. + You should not directly instantiate this, use :func:`open_nursery`. + ''' + + __slots__ = ('_closed', '_children', '_scope', '_counters', '_callbacks', '_gc_in_every', '_n_until_gc', ) + + def __init__(self, scope, counter, daemon_counter, gc_in_every): + self._gc_in_every = self._n_until_gc = gc_in_every + self._closed = False + self._children = [] + self._scope = scope + self._counters = (daemon_counter, counter, ) + self._callbacks = ( + partial(_on_child_end__ver_all, scope, daemon_counter), + partial(_on_child_end__ver_all, scope, counter), + ) + + def start(self, aw: Aw_or_Task, /, *, daemon=False) -> Task: + ''' + *Immediately* start a Task/Awaitable under the supervision of the nursery. + + If the argument is a :class:`Task`, itself will be returned. If it's an :class:`typing.Awaitable`, + it will be wrapped in a Task, and that Task will be returned. + + The ``daemon`` parameter acts like the one in the :mod:`threading` module. + When only daemon tasks are left, they get cancelled, and the nursery closes. + ''' + if self._closed: + raise InvalidStateError("Nursery has been already closed") + if not self._n_until_gc: + self._collect_garbage() + self._n_until_gc = self._gc_in_every + self._n_until_gc -= 1 + child = aw if isinstance(aw, Task) else Task(aw) + child._suppresses_exc = True + child._on_end = self._callbacks[not daemon] + self._counters[not daemon].increase() + self._children.append(child) + return start(child) + + def _collect_garbage(self, STARTED=TaskState.STARTED): + self._children = [ + c for c in self._children + if c.state is STARTED or c._exc_caught is not None + ] + + def close(self): + '''Cancel all the child tasks in the nursery as soon as possible. ''' + self._closed = True + self._scope.cancel() + + @property + def closed(self) -> bool: + return self._closed + + +@asynccontextmanager +async def open_nursery(*, _gc_in_every=1000) -> T.AsyncIterator[Nursery]: + ''' + Similar to :func:`trio.open_nursery`. + + .. code-block:: + + async with open_nursery() as nursery: + nursery.start(async_fn1()) + nursery.start(async_fn2(), daemon=True) + ''' + exc = None + parent = await current_task() + counter = TaskCounter() + daemon_counter = TaskCounter() + + try: + with CancelScope(parent) as scope: + nursery = Nursery(scope, counter, daemon_counter, _gc_in_every) + yield nursery + await counter.to_be_zero() + except Exception as e: + exc = e + finally: + nursery._closed = True + children = nursery._children + for c in children: + c.cancel() + try: + parent._cancel_disabled += 1 + await daemon_counter.to_be_zero() + await counter.to_be_zero() + finally: + parent._cancel_disabled -= 1 + excs = tuple( + e for e in itertools.chain((exc, ), (c._exc_caught for c in children)) + if e is not None + ) + if excs: + raise ExceptionGroup("Nursery", excs) + if (parent._cancel_level is not None) and (not parent._cancel_disabled): + await sleep_forever() + assert False, potential_bug_msg + + +# ----------------------------------------------------------------------------- +# Aliases +# ----------------------------------------------------------------------------- +run_as_primary = run_as_main +run_as_secondary = run_as_daemon +and_ = wait_all #: An alias for :func:`wait_all`. +or_ = wait_any #: An alias for :func:`wait_any`. +move_on_when = wait_any_cm #: An alias for :func:`wait_any_cm`. diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/INSTALLER b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/LICENSE b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/LICENSE new file mode 100644 index 0000000..4f113c4 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/LICENSE @@ -0,0 +1,7 @@ +Copyright 2019 gottadiveintopython + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/METADATA b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/METADATA new file mode 100644 index 0000000..1cc4a2c --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/METADATA @@ -0,0 +1,177 @@ +Metadata-Version: 2.1 +Name: asynckivy +Version: 0.6.4 +Summary: Async library for Kivy +Home-page: https://github.com/asyncgui/asynckivy +License: MIT +Keywords: async,kivy +Author: Nattōsai Mitō +Author-email: flow4re2c@gmail.com +Requires-Python: >=3.9,<4.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries +Requires-Dist: asyncgui (>=0.6,<0.7) +Project-URL: Repository, https://github.com/asyncgui/asynckivy +Description-Content-Type: text/markdown + +# AsyncKivy + +[Youtube](https://www.youtube.com/playlist?list=PLNdhqAjzeEGjTpmvNck4Uykps8s9LmRTJ) +[日本語doc](README_jp.md) + +`asynckivy` is an async library that saves you from ugly callback-style code, +like most of async libraries do. +Let's say you want to do: + +1. `print('A')` +1. wait for 1sec +1. `print('B')` +1. wait for a button to be pressed +1. `print('C')` + +in that order. +Your code would look like this: + +```python +from kivy.clock import Clock + +def what_you_want_to_do(button): + print('A') + + def one_sec_later(__): + print('B') + button.bind(on_press=on_button_press) + Clock.schedule_once(one_sec_later, 1) + + def on_button_press(button): + button.unbind(on_press=on_button_press) + print('C') + +what_you_want_to_do(...) +``` + +It's not easy to understand. +If you use `asynckivy`, the code above will become: + +```python +import asynckivy as ak + +async def what_you_want_to_do(button): + print('A') + await ak.sleep(1) + print('B') + await ak.event(button, 'on_press') + print('C') + +ak.start(what_you_want_to_do(...)) +``` + +## Installation + +Pin the minor version. + +```text +poetry add asynckivy@~0.6 +pip install "asynckivy>=0.6,<0.7" +``` + +## Usage + +```python +import asynckivy as ak + +async def some_task(button): + # waits for 2 seconds to elapse + dt = await ak.sleep(2) + print(f'{dt} seconds have elapsed') + + # waits for a button to be pressed + await ak.event(button, 'on_press') + + # waits for the value of 'button.x' to change + __, x = await ak.event(button, 'x') + print(f'button.x is now {x}') + + # waits for the value of 'button.x' to become greater than 100 + if button.x <= 100: + __, x = await ak.event(button, 'x', filter=lambda __, x: x>100) + print(f'button.x is now {x}') + + # waits for either 5 seconds to elapse or a button to be pressed. + # i.e. waits at most 5 seconds for a button to be pressed + tasks = await ak.wait_any( + ak.sleep(5), + ak.event(button, 'on_press'), + ) + print("Timeout" if tasks[0].finished else "The button was pressed") + + # same as the above + async with ak.move_on_after(5) as bg_task: + await ak.event(button, 'on_press') + print("Timeout" if bg_task.finished else "The button was pressed") + + # waits for both 5 seconds to elapse and a button to be pressed. + tasks = await ak.wait_all( + ak.sleep(5), + ak.event(button, 'on_press'), + ) + + # nest as you want. + # waits for a button to be pressed, and either 5 seconds to elapse or 'other_async_func' to complete. + tasks = await ak.wait_all( + ak.event(button, 'on_press'), + ak.wait_any( + ak.sleep(5), + other_async_func(), + ), + ) + child_tasks = tasks[1].result + print("5 seconds elapsed" if child_tasks[0].finished else "other_async_func has completed") + +ak.start(some_task(some_button)) +``` + +For more details, read the [documentation](https://asyncgui.github.io/asynckivy/). + +## Tested on + +- CPython 3.8 + Kivy 2.3.0 +- CPython 3.9 + Kivy 2.3.0 +- CPython 3.10 + Kivy 2.3.0 +- CPython 3.11 + Kivy 2.3.0 +- CPython 3.12 + Kivy 2.3.0 (3.12.0 is not supported due to [this issue](https://github.com/python/cpython/issues/111058)) + +## Why this even exists + +Kivy supports two legitimate async libraries, [asyncio][asyncio] and [Trio][trio], starting from version 2.0.0, so developing another one seems like [reinventing the wheel][reinventing]. +Actually, I started this one just to learn how the async/await syntax works, so it initially was "reinventing the wheel". + +But after playing with Trio and Kivy for a while, I noticed that Trio is not suitable for the situation where fast reactions are required e.g. touch events. +The same is true of asyncio. +You can confirm that by running `investigation/why_xxx_is_not_suitable_for_handling_touch_events.py`, and mashing a mouse button as quickly as possible. +You'll see sometimes `up` is not paired with `down`. +You'll see the coordinates aren't relative to the `RelativeLayout` even though the `target` belongs to it. + +The cause of those problems is that `trio.Event.set()` and `asyncio.Event.set()` don't *immediately* resume the tasks waiting for the `Event` to be set. +They just schedule the tasks to resume. +Same thing can be said to `nursery.start_soon()` and `asyncio.create_task()`. + +Trio and asyncio are async **I/O** libraries after all. +They probably don't have to immediately resumes/starts tasks, which I think necessary for touch handling in Kivy. +(If you fail to handle touches promptly, their state might undergo changes, leaving no time to wait for tasks to resume/start). +Their core design might not be suitable for GUI in the first place. +That's why I'm still developing this `asynckivy` library to this day. + +[asyncio]:https://docs.python.org/3/library/asyncio.html +[trio]:https://trio.readthedocs.io/en/stable/ +[reinventing]:https://en.wikipedia.org/wiki/Reinventing_the_wheel + diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/RECORD b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/RECORD new file mode 100644 index 0000000..b40079e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/RECORD @@ -0,0 +1,31 @@ +asynckivy-0.6.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +asynckivy-0.6.4.dist-info/LICENSE,sha256=2jxtLdIltPBFl7vb9bLmquYKRJiyidCvO_DhOgFWIe8,1059 +asynckivy-0.6.4.dist-info/METADATA,sha256=P3n9htW_NrrBuy5BHojYoUgkePfNn2y4h__APrwdK5w,5876 +asynckivy-0.6.4.dist-info/RECORD,, +asynckivy-0.6.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88 +asynckivy/__init__.py,sha256=Ttzd2FLPuQEAPrNINViuIK8Yj4i5swu2wKKkl0vCMoA,1285 +asynckivy/__pycache__/__init__.cpython-311.pyc,, +asynckivy/__pycache__/_anim_attrs.cpython-311.pyc,, +asynckivy/__pycache__/_anim_with_xxx.cpython-311.pyc,, +asynckivy/__pycache__/_animation.cpython-311.pyc,, +asynckivy/__pycache__/_event.cpython-311.pyc,, +asynckivy/__pycache__/_exceptions.cpython-311.pyc,, +asynckivy/__pycache__/_interpolate.cpython-311.pyc,, +asynckivy/__pycache__/_n_frames.cpython-311.pyc,, +asynckivy/__pycache__/_sleep.cpython-311.pyc,, +asynckivy/__pycache__/_threading.cpython-311.pyc,, +asynckivy/__pycache__/_touch.cpython-311.pyc,, +asynckivy/__pycache__/_utils.cpython-311.pyc,, +asynckivy/__pycache__/vanim.cpython-311.pyc,, +asynckivy/_anim_attrs.py,sha256=VCWAPMFtwZU4HOdfhcFDR1JIvjMDW-nuaSvn2MAnu3Q,3838 +asynckivy/_anim_with_xxx.py,sha256=IzJ-NYCJI9mFgOH1oRtZ0VLcsIY7TNjf88VuaoU9Lo4,3516 +asynckivy/_animation.py,sha256=6J2vwvXXJJegLk48nx4KrHYrZM5uZiulE6pTH4n9A4U,3589 +asynckivy/_event.py,sha256=_1O6bKZXZKkV5a-i4Qo3pmZHU1jCobf6bwIS9TUJG-U,1711 +asynckivy/_exceptions.py,sha256=Pg2qkGaVbF2OgVrfcxqOPobEgvavAPYr4RZRDtvyV-o,764 +asynckivy/_interpolate.py,sha256=ojjQhiND4-rkTol5q79R2eblXL-2XhQPQMzWovSkDoU,2252 +asynckivy/_n_frames.py,sha256=Rwtc-9nVBGT-IT5dRwQBowAS0ENBEnrzQKxVhPp4qoE,868 +asynckivy/_sleep.py,sha256=qiNN_4PUJiz8pxnLqUykHSLcZhSLuLnHnfekTTEKDWQ,3354 +asynckivy/_threading.py,sha256=onV5x3H0qfDYzHs3PRIprxlJR3dpSbdZWjjChRXI35E,1621 +asynckivy/_touch.py,sha256=C_Z3qoYNVPE-KWYrc56Q4Uj79sLK2V0L_6Pj6ZTdPYU,7669 +asynckivy/_utils.py,sha256=qr4__ywCImXrfNdxnxAfn7PLux1KGAwWud_Pzb9ixVo,7705 +asynckivy/vanim.py,sha256=-9kRfu3yJsKX8Z4xp8Wm6cOfUxzg54oeohUDV1FKktA,608 diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/WHEEL b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/WHEEL new file mode 100644 index 0000000..d73ccaa --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy-0.6.4.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: poetry-core 1.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__init__.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/__init__.py new file mode 100644 index 0000000..9ea8d91 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/__init__.py @@ -0,0 +1,42 @@ +__all__ = ( + 'MotionEventAlreadyEndedError', + 'anim_attrs', + 'anim_attrs_abbr', + 'anim_with_dt', + 'anim_with_dt_et', + 'anim_with_dt_et_ratio', + 'anim_with_et', + 'anim_with_ratio', + 'animate', + 'create_texture_from_text', + 'event', + 'fade_transition', + 'interpolate', + 'move_on_after', + 'n_frames', + 'repeat_sleeping', + 'rest_of_touch_events', + 'run_in_executor', + 'run_in_thread', + 'sleep', + 'sleep_free', + 'suppress_event', + 'sync_attr', + 'sync_attrs', + 'touch_up_event', + 'transform', + 'watch_touch', +) + +from asyncgui import * +from ._exceptions import MotionEventAlreadyEndedError +from ._sleep import sleep, sleep_free, repeat_sleeping, move_on_after +from ._event import event +from ._anim_with_xxx import anim_with_dt, anim_with_et, anim_with_ratio, anim_with_dt_et, anim_with_dt_et_ratio +from ._animation import animate +from ._anim_attrs import anim_attrs, anim_attrs_abbr +from ._interpolate import interpolate, fade_transition +from ._touch import watch_touch, rest_of_touch_events, rest_of_touch_moves, touch_up_event +from ._threading import run_in_executor, run_in_thread +from ._n_frames import n_frames +from ._utils import transform, suppress_event, create_texture_from_text, sync_attr, sync_attrs diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..cb01734 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_anim_attrs.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_anim_attrs.cpython-311.pyc new file mode 100644 index 0000000..0c78636 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_anim_attrs.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_anim_with_xxx.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_anim_with_xxx.cpython-311.pyc new file mode 100644 index 0000000..2a009b5 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_anim_with_xxx.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_animation.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_animation.cpython-311.pyc new file mode 100644 index 0000000..d8a1b54 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_animation.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_event.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_event.cpython-311.pyc new file mode 100644 index 0000000..986515e Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_event.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_exceptions.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_exceptions.cpython-311.pyc new file mode 100644 index 0000000..2ded891 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_exceptions.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_interpolate.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_interpolate.cpython-311.pyc new file mode 100644 index 0000000..07eda71 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_interpolate.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_n_frames.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_n_frames.cpython-311.pyc new file mode 100644 index 0000000..5921471 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_n_frames.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_sleep.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_sleep.cpython-311.pyc new file mode 100644 index 0000000..aa10265 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_sleep.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_threading.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_threading.cpython-311.pyc new file mode 100644 index 0000000..fdefe0c Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_threading.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_touch.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_touch.cpython-311.pyc new file mode 100644 index 0000000..0bfeeba Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_touch.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_utils.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_utils.cpython-311.pyc new file mode 100644 index 0000000..03c56a1 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/_utils.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/vanim.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/vanim.cpython-311.pyc new file mode 100644 index 0000000..881c65c Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/asynckivy/__pycache__/vanim.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_attrs.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_attrs.py new file mode 100644 index 0000000..da85092 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_attrs.py @@ -0,0 +1,114 @@ +__all__ = ('anim_attrs', 'anim_attrs_abbr', ) +import typing as T +import types +from functools import partial +import kivy.clock +from kivy.animation import AnimationTransition +import asyncgui + + +def _update(setattr, zip, min, obj, duration, transition, output_seq_type, anim_params, task, p_time, dt): + time = p_time[0] + dt + p_time[0] = time + + # calculate progression + progress = min(1., time / duration) + t = transition(progress) + + # apply progression on obj + for attr_name, org_value, slope, is_seq in anim_params: + if is_seq: + new_value = output_seq_type( + slope_elem * t + org_elem + for org_elem, slope_elem in zip(org_value, slope) + ) + setattr(obj, attr_name, new_value) + else: + setattr(obj, attr_name, slope * t + org_value) + + # time to stop ? + if progress >= 1.: + task._step() + return False + + +_update = partial(_update, setattr, zip, min) + + +@types.coroutine +def _anim_attrs( + obj, duration, step, transition, output_seq_type, animated_properties, + getattr=getattr, isinstance=isinstance, tuple=tuple, str=str, partial=partial, native_seq_types=(tuple, list), + zip=zip, Clock=kivy.clock.Clock, AnimationTransition=AnimationTransition, + _update=_update, _current_task=asyncgui._current_task, _sleep_forever=asyncgui._sleep_forever, /): + if isinstance(transition, str): + transition = getattr(AnimationTransition, transition) + + # get current values & calculate slopes + anim_params = tuple( + ( + org_value := getattr(obj, attr_name), + is_seq := isinstance(org_value, native_seq_types), + ( + org_value := tuple(org_value), + slope := tuple(goal_elem - org_elem for goal_elem, org_elem in zip(goal_value, org_value)), + ) if is_seq else (slope := goal_value - org_value), + ) and (attr_name, org_value, slope, is_seq, ) + for attr_name, goal_value in animated_properties.items() + ) + + try: + clock_event = Clock.schedule_interval( + partial(_update, obj, duration, transition, output_seq_type, anim_params, (yield _current_task)[0][0], + [0., ]), + step, + ) + yield _sleep_forever + finally: + clock_event.cancel() + + +def anim_attrs(obj, *, duration=1.0, step=0, transition=AnimationTransition.linear, output_seq_type=tuple, + **animated_properties) -> T.Awaitable: + ''' + Animates attibutes of any object. + + .. code-block:: + + import types + + obj = types.SimpleNamespace(x=0, size=(200, 300)) + await anim_attrs(obj, x=100, size=(400, 400)) + + The ``output_seq_type`` parameter: + + .. code-block:: + + obj = types.SimpleNamespace(size=(200, 300)) + await anim_attrs(obj, size=(400, 400), output_seq_type=list) + assert type(obj.size) is list + + .. warning:: + + Unlike :class:`kivy.animation.Animation`, this one does not support dictionary-type and nested-sequence. + + .. code-block:: + + await anim_attrs(obj, pos_hint={'x': 1.}) # not supported + await anim_attrs(obj, nested_sequence=[[10, 20, ]]) # not supported + + await anim_attrs(obj, color=(1, 0, 0, 1), pos=(100, 200)) # OK + + .. versionadded:: 0.6.1 + ''' + return _anim_attrs(obj, duration, step, transition, output_seq_type, animated_properties) + + +def anim_attrs_abbr(obj, *, d=1.0, s=0, t=AnimationTransition.linear, output_seq_type=tuple, + **animated_properties) -> T.Awaitable: + ''' + :func:`anim_attrs` cannot animate attributes named ``step``, ``duration`` and ``transition`` but this one can. + + .. versionadded:: 0.6.1 + ''' + return _anim_attrs(obj, d, s, t, output_seq_type, animated_properties) diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_with_xxx.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_with_xxx.py new file mode 100644 index 0000000..97f104c --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_with_xxx.py @@ -0,0 +1,146 @@ +__all__ = ( + 'anim_with_dt', 'anim_with_et', 'anim_with_ratio', 'anim_with_dt_et', 'anim_with_dt_et_ratio', +) + +from ._sleep import repeat_sleeping + + +async def anim_with_dt(*, step=0): + ''' + An async form of :meth:`kivy.clock.Clock.schedule_interval`. The following callback-style code: + + .. code-block:: + + def callback(dt): + print(dt) + if some_condition: + return False + + Clock.schedule_interval(callback, 0.1) + + is equivalent to the following async-style code: + + .. code-block:: + + async for dt in anim_with_dt(step=0.1): + print(dt) + if some_condition: + break + + .. versionadded:: 0.6.1 + ''' + async with repeat_sleeping(step=step) as sleep: + while True: + yield await sleep() + + +async def anim_with_et(*, step=0): + ''' + Same as :func:`anim_with_dt` except this one generates the total elapsed time of the loop instead of the elapsed + time between frames. + + .. code-block:: + + timeout = 3.0 + async for et in anim_with_et(...): + ... + if et > timeout: + break + + You can calculate ``et`` by yourself if you want to: + + .. code-block:: + + et = 0. + timeout = 3.0 + async for dt in anim_with_dt(...): + et += dt + ... + if et > timeout: + break + + which should be as performant as the former. + + .. versionadded:: 0.6.1 + ''' + et = 0. + async with repeat_sleeping(step=step) as sleep: + while True: + et += await sleep() + yield et + + +async def anim_with_dt_et(*, step=0): + ''' + :func:`anim_with_dt` and :func:`anim_with_et` combined. + + .. code-block:: + + async for dt, et in anim_with_dt_et(...): + ... + + .. versionadded:: 0.6.1 + ''' + et = 0. + async with repeat_sleeping(step=step) as sleep: + while True: + dt = await sleep() + et += dt + yield dt, et + + +async def anim_with_ratio(*, duration=1., step=0): + ''' + Same as :func:`anim_with_et` except this one generates the total progression ratio of the loop. + + .. code-block:: + + async for p in anim_with_ratio(duration=3.0): + print(p * 100, "%") + + If you want to progress at a non-consistant rate, :class:`kivy.animation.AnimationTransition` may be helpful. + + .. code-block:: + + from kivy.animation import AnimationTransition + + in_cubic = AnimationTransition.in_cubic + + async for p in anim_with_ratio(duration=3.0): + p = in_cubic(p) + print(p * 100, "%") + + .. versionadded:: 0.6.1 + ''' + async with repeat_sleeping(step=step) as sleep: + if not duration: + await sleep() + yield 1.0 + return + et = 0. + while et < duration: + et += await sleep() + yield et / duration + + +async def anim_with_dt_et_ratio(*, duration=1., step=0): + ''' + :func:`anim_with_dt`, :func:`anim_with_et` and :func:`anim_with_ratio` combined. + + .. code-block:: + + async for dt, et, p in anim_with_dt_et_ratio(...): + ... + + .. versionadded:: 0.6.1 + ''' + async with repeat_sleeping(step=step) as sleep: + if not duration: + dt = await sleep() + yield dt, dt, 1.0 + return + et = 0. + while et < duration: + dt = await sleep() + et += dt + yield dt, et, et / duration diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_animation.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_animation.py new file mode 100644 index 0000000..9c19a68 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_animation.py @@ -0,0 +1,115 @@ +__all__ = ('animate', ) +import typing as T +import types +from functools import partial +from kivy.clock import Clock +from kivy.animation import AnimationTransition +from asyncgui import _sleep_forever, _current_task + + +@types.coroutine +def animate(obj, *, duration=1.0, step=0, transition=AnimationTransition.linear, **animated_properties) -> T.Awaitable: + ''' + Animates attibutes of any object. This is basically an async form of :class:`kivy.animation.Animation`. + + .. code-block:: + + import types + + obj = types.SimpleNamespace(x=0, size=(200, 300, )) + await animate(obj, x=100, size=(400, 400)) + + Kivy has two compound animations, :class:`kivy.animation.Sequence` and :class:`kivy.animation.Parallel`. + You can achieve the same functionality as them in asynckivy as follows: + + .. code-block:: + + import asynckivy as ak + + async def sequential_animation(widget): + await ak.animate(widget, x=100) + await ak.animate(widget, x=0) + + async def parallel_animation(widget): + await ak.wait_all( + ak.animate(widget, x=100), + ak.animate(widget, y=100, duration=2), + ) + + .. deprecated:: 0.6.1 + + This will be removed before version 1.0.0. + Use :func:`asynckivy.anim_attrs` or :func:`asynckivy.anim_attrs_abbr` instead. + ''' + if not duration: + for key, value in animated_properties.items(): + setattr(obj, key, value) + return + if isinstance(transition, str): + transition = getattr(AnimationTransition, transition) + + # get current values + properties = {} + for key, value in animated_properties.items(): + original_value = getattr(obj, key) + if isinstance(original_value, (tuple, list)): + original_value = original_value[:] + elif isinstance(original_value, dict): + original_value = original_value.copy() + properties[key] = (original_value, value) + + try: + clock_event = Clock.schedule_interval( + partial(_update, obj, duration, transition, properties, (yield _current_task)[0][0], [0., ]), + step, + ) + yield _sleep_forever + finally: + clock_event.cancel() + + +def _calculate(isinstance, list, tuple, dict, range, len, a, b, t): + '''The logic of this function is identical to 'kivy.animation.Animation._calculate()' + ''' + if isinstance(a, list) or isinstance(a, tuple): + if isinstance(a, list): + tp = list + else: + tp = tuple + return tp([_calculate(a[x], b[x], t) for x in range(len(a))]) + elif isinstance(a, dict): + d = {} + for x in a: + if x not in b: + # User requested to animate only part of the dict. + # Copy the rest + d[x] = a[x] + else: + d[x] = _calculate(a[x], b[x], t) + return d + else: + return (a * (1. - t)) + (b * t) + + +def _update(setattr, _calculate, obj, duration, transition, properties, task, p_time, dt): + time = p_time[0] + dt + p_time[0] = time + + # calculate progression + progress = min(1., time / duration) + t = transition(progress) + + # apply progression on obj + for key, values in properties.items(): + a, b = values + value = _calculate(a, b, t) + setattr(obj, key, value) + + # time to stop ? + if progress >= 1.: + task._step() + return False + + +_calculate = partial(_calculate, isinstance, list, tuple, dict, range, len) +_update = partial(_update, setattr, _calculate) diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_event.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_event.py new file mode 100644 index 0000000..b03637b --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_event.py @@ -0,0 +1,57 @@ +__all__ = ('event', ) + +import typing as T +import types +from functools import partial +from asyncgui import _current_task, _sleep_forever + + +@types.coroutine +def event(event_dispatcher, event_name, *, filter=None, stop_dispatching=False) -> T.Awaitable[tuple]: + ''' + Returns an awaitable that can be used to wait for: + + * a Kivy event to occur. + * a Kivy property's value to change. + + .. code-block:: + + # Wait for a button to be pressed. + await event(button, 'on_press') + + # Wait for an 'on_touch_down' event to occur. + __, touch = await event(widget, 'on_touch_down') + + # Wait for 'widget.x' to change. + __, x = await ak.event(widget, 'x') + + + The ``filter`` parameter: + + .. code-block:: + + # Wait for an 'on_touch_down' event to occur inside a widget. + __, touch = await event(widget, 'on_touch_down', filter=lambda w, t: w.collide_point(*t.opos)) + + # Wait for 'widget.x' to become greater than 100. + if widget.x <= 100: + await event(widget, 'x', filter=lambda __, x: x > 100) + + The ``stop_dispatching`` parameter: + + It only works for events not for properties. + See :ref:`kivys-event-system` for details. + ''' + task = (yield _current_task)[0][0] + bind_id = event_dispatcher.fbind(event_name, partial(_callback, filter, task, stop_dispatching)) + assert bind_id # check if binding succeeded + try: + return (yield _sleep_forever)[0] + finally: + event_dispatcher.unbind_uid(event_name, bind_id) + + +def _callback(filter, task, stop_dispatching, *args, **kwargs): + if (filter is None) or filter(*args, **kwargs): + task._step(*args) + return stop_dispatching diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_exceptions.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_exceptions.py new file mode 100644 index 0000000..3fc7eaf --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_exceptions.py @@ -0,0 +1,27 @@ +__all__ = ( + 'MotionEventAlreadyEndedError', +) + + +class MotionEventAlreadyEndedError(Exception): + ''' + This error occurs when an already-ended touch is passed to an asynckivy API that expects an ongoing touch. + For instance: + + .. code-block:: + :emphasize-lines: 4 + + import asynckivy as ak + + class MyWidget(Widget): + def on_touch_up(self, touch): # not 'on_touch_down', oops! + ak.start(self.handle_touch(touch)) + return True + + async def handle_touch(self, touch): + try: + async for __ in ak.rest_of_touch_events(widget, touch): + ... + except ak.MotionEventAlreadyEndedError: + ... + ''' diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_interpolate.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_interpolate.py new file mode 100644 index 0000000..193e520 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_interpolate.py @@ -0,0 +1,75 @@ +__all__ = ('interpolate', 'fade_transition', ) +import typing as T +from contextlib import asynccontextmanager +from kivy.animation import AnimationTransition + +from ._anim_with_xxx import anim_with_ratio + + +linear = AnimationTransition.linear + + +async def interpolate(start, end, *, duration=1.0, step=0, transition=linear) -> T.AsyncIterator: + ''' + Interpolates between the values ``start`` and ``end`` in an async-manner. + Inspired by wasabi2d's interpolate_. + + .. code-block:: + + async for v in interpolate(0, 100, duration=1.0, step=.3): + print(int(v)) + + ============ ====== + elapsed time output + ============ ====== + 0 sec 0 + 0.3 sec 30 + 0.6 sec 60 + 0.9 sec 90 + **1.2 sec** 100 + ============ ====== + + .. _interpolate: https://wasabi2d.readthedocs.io/en/stable/coros.html#clock.coro.interpolate + ''' + if isinstance(transition, str): + transition = getattr(AnimationTransition, transition) + + slope = end - start + yield transition(0.) * slope + start + async for p in anim_with_ratio(step=step, duration=duration): + if p >= 1.0: + break + yield transition(p) * slope + start + yield transition(1.) * slope + start + + +@asynccontextmanager +async def fade_transition(*widgets, duration=1.0, step=0) -> T.AsyncContextManager: + ''' + Returns an async context manager that: + + * fades-out the given widgets on ``__aenter__``. + * fades-in the given widgets on ``__aexit__``. + + .. code-block:: + + async with fade_transition(widget1, widget2): + ... + + The ``widgets`` don't have to be actual Kivy widgets. + Anything that has an attribute named ``opacity`` would work. + ''' + half_duration = duration / 2. + org_opas = tuple(w.opacity for w in widgets) + try: + async for p in anim_with_ratio(duration=half_duration, step=step): + p = 1.0 - p + for w, o in zip(widgets, org_opas): + w.opacity = p * o + yield + async for p in anim_with_ratio(duration=half_duration, step=step): + for w, o in zip(widgets, org_opas): + w.opacity = p * o + finally: + for w, o in zip(widgets, org_opas): + w.opacity = o diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_n_frames.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_n_frames.py new file mode 100644 index 0000000..7d91868 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_n_frames.py @@ -0,0 +1,43 @@ +__all__ = ('n_frames', ) + +import typing as T +import types +from kivy.clock import Clock +from asyncgui import _current_task, _sleep_forever + + +@types.coroutine +def n_frames(n: int) -> T.Awaitable: + ''' + Waits for a specified number of frames. + + .. code-block:: + + await n_frames(2) + + If you want to wait for one frame, :func:`asynckivy.sleep` is preferable for a performance reason. + + .. code-block:: + + await sleep(0) + ''' + if n < 0: + raise ValueError(f"Waiting for {n} frames doesn't make sense.") + if not n: + return + + task = (yield _current_task)[0][0] + + def callback(dt): + nonlocal n + n -= 1 + if not n: + task._step() + return False + + clock_event = Clock.schedule_interval(callback, 0) + + try: + yield _sleep_forever + finally: + clock_event.cancel() diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_sleep.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_sleep.py new file mode 100644 index 0000000..d25e33e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_sleep.py @@ -0,0 +1,125 @@ +__all__ = ('sleep', 'sleep_free', 'repeat_sleeping', 'move_on_after', ) + +import typing as T +import types + +from kivy.clock import Clock +from asyncgui import _current_task, _sleep_forever, move_on_when, Task, Cancelled + + +@types.coroutine +def sleep(duration) -> T.Awaitable[float]: + ''' + An async form of :meth:`kivy.clock.Clock.schedule_once`. + + .. code-block:: + + dt = await sleep(5) # wait for 5 seconds + ''' + task = (yield _current_task)[0][0] + clock_event = Clock.create_trigger(task._step, duration, False, False) + clock_event() + + try: + return (yield _sleep_forever)[0][0] + except Cancelled: + clock_event.cancel() + raise + + +@types.coroutine +def sleep_free(duration) -> T.Awaitable[float]: + ''' + An async form of :meth:`kivy.clock.Clock.schedule_once_free`. + + .. code-block:: + + dt = await sleep_free(5) # wait for 5 seconds + ''' + task = (yield _current_task)[0][0] + clock_event = Clock.create_trigger_free(task._step, duration, False, False) + clock_event() + + try: + return (yield _sleep_forever)[0][0] + except Cancelled: + clock_event.cancel() + raise + + +class repeat_sleeping: + ''' + Returns an async context manager that provides an efficient way to repeat sleeping. + + When there is a piece of code like this: + + .. code-block:: + + while True: + await sleep(0) + ... + + it can be translated to: + + .. code-block:: + + async with repeat_sleeping(step=0) as sleep: + while True: + await sleep() + ... + + The latter is more suitable for situations requiring frequent sleeps, such as moving an object in every frame. + + **Restriction** + + You are not allowed to perform any kind of async operations inside the with-block except you can + ``await`` the return value of the function that is bound to the identifier of the as-clause. + + .. code-block:: + + async with repeat_sleeping(step=0) as sleep: + await sleep() # OK + await something_else # NOT ALLOWED + async with async_context_manager: # NOT ALLOWED + ... + async for __ in async_iterator: # NOT ALLOWED + ... + ''' + + __slots__ = ('_step', '_trigger', ) + + @types.coroutine + def _sleep(_f=_sleep_forever): + return (yield _f)[0][0] + + def __init__(self, *, step=0): + self._step = step + + @types.coroutine + def __aenter__(self, _sleep=_sleep) -> T.Awaitable[T.Callable[[], T.Awaitable[float]]]: + task = (yield _current_task)[0][0] + self._trigger = Clock.create_trigger(task._step, self._step, True, False) + self._trigger() + return _sleep + + async def __aexit__(self, exc_type, exc_val, exc_tb): + self._trigger.cancel() + + +def move_on_after(seconds: float) -> T.AsyncContextManager[Task]: + ''' + Returns an async context manager that applies a time limit to its code block, + like :func:`trio.move_on_after` does. + + .. code-block:: + + async with move_on_after(seconds) as bg_task: + ... + if bg_task.finished: + print("The code block was interrupted due to a timeout") + else: + print("The code block exited gracefully.") + + .. versionadded:: 0.6.1 + ''' + return move_on_when(sleep(seconds)) diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_threading.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_threading.py new file mode 100644 index 0000000..7094fad --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_threading.py @@ -0,0 +1,64 @@ +__all__ = ('run_in_thread', 'run_in_executor', ) +import typing as T +from threading import Thread +from concurrent.futures import ThreadPoolExecutor +from kivy.clock import Clock +import asyncgui + + +def _wrapper(func, ev): + ret = None + exc = None + try: + ret = func() + except Exception as e: + exc = e + finally: + Clock.schedule_once(lambda __: ev.fire(ret, exc)) + + +async def run_in_thread(func, *, daemon=None) -> T.Awaitable: + ''' + Creates a new thread, runs a function within it, then waits for the completion of that function. + + .. code-block:: + + return_value = await run_in_thread(func) + + See :ref:`io-in-asynckivy` for details. + ''' + ev = asyncgui.AsyncEvent() + Thread( + name='asynckivy.run_in_thread', + target=_wrapper, daemon=daemon, args=(func, ev, ), + ).start() + ret, exc = (await ev.wait())[0] + if exc is not None: + raise exc + return ret + + +async def run_in_executor(executor: ThreadPoolExecutor, func) -> T.Awaitable: + ''' + Runs a function within a :class:`concurrent.futures.ThreadPoolExecutor`, and waits for the completion of the + function. + + .. code-block:: + + executor = ThreadPoolExecutor() + ... + return_value = await run_in_executor(executor, func) + + See :ref:`io-in-asynckivy` for details. + ''' + ev = asyncgui.AsyncEvent() + future = executor.submit(_wrapper, func, ev) + try: + ret, exc = (await ev.wait())[0] + except asyncgui.Cancelled: + future.cancel() + raise + assert future.done() + if exc is not None: + raise exc + return ret diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_touch.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_touch.py new file mode 100644 index 0000000..469e4cb --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_touch.py @@ -0,0 +1,196 @@ +__all__ = ('watch_touch', 'rest_of_touch_moves', 'rest_of_touch_events', 'touch_up_event', ) + +import typing as T +import types +from functools import partial +from asyncgui import wait_any, current_task +from ._exceptions import MotionEventAlreadyEndedError +from ._sleep import sleep +from ._event import event + + +class watch_touch: + ''' + Returns an async context manager that provides an easy way to handle touch events. + + .. code-block:: + + async with watch_touch(widget, touch) as in_progress: + while await in_progress(): + print('on_touch_move') + print('on_touch_up') + + The ``await in_progress()`` waits for either an ``on_touch_move`` event or an ``on_touch_up`` event to occur, and + returns True or False respectively when they occurred. + + **Caution** + + * You are not allowed to perform any kind of async operations inside the with-block except ``await in_progress()``. + + .. code-block:: + + async with watch_touch(widget, touch) as in_progress: + await in_progress() # OK + await something_else # NOT ALLOWED + async with async_context_manager: # NOT ALLOWED + ... + async for __ in async_iterator: # NOT ALLOWED + ... + + * If the ``widget`` is the type of widget that grabs touches by itself, such as :class:`kivy.uix.button.Button`, + you probably want to set the ``stop_dispatching`` parameter to True in most cases. + * There are widgets/behaviors that can simulate a touch (e.g. :class:`kivy.uix.scrollview.ScrollView`, + :class:`kivy.uix.behaviors.DragBehavior` and ``kivy_garden.draggable.KXDraggableBehavior``). + If many such widgets are in the parent stack of the ``widget``, this API might mistakenly raise a + :exc:`asynckivy.MotionEventAlreadyEndedError`. If that happens, increase the ``timeout`` parameter. + ''' + __slots__ = ('_widget', '_touch', '_stop_dispatching', '_timeout', '_uid_up', '_uid_move', '_no_cleanup', ) + + def __init__(self, widget, touch, *, stop_dispatching=False, timeout=1.): + self._widget = widget + self._touch = touch + self._stop_dispatching = stop_dispatching + self._timeout = timeout + self._no_cleanup = False + + def _on_touch_up_sd(step, touch, w, t): + if t is touch: + if t.grab_current is w: + t.ungrab(w) + step(False) + return True + + def _on_touch_move_sd(step, touch, w, t): + if t is touch: + if t.grab_current is w: + step(True) + return True + + def _on_touch_up(step, touch, w, t): + if t.grab_current is w and t is touch: + t.ungrab(w) + step(False) + return True + + def _on_touch_move(step, touch, w, t): + if t.grab_current is w and t is touch: + step(True) + return True + + _callbacks = ((_on_touch_up_sd, _on_touch_move_sd, ), (_on_touch_up, _on_touch_move, ), ) + del _on_touch_up, _on_touch_move, _on_touch_up_sd, _on_touch_move_sd + + @staticmethod + @types.coroutine + def _true_if_touch_move_false_if_touch_up() -> bool: + return (yield lambda step_coro: None)[0][0] + + @staticmethod + @types.coroutine + def _always_false() -> bool: + return False + yield # just to make this function a generator function + + async def __aenter__(self) -> T.Awaitable[T.Callable[[], T.Awaitable[bool]]]: + touch = self._touch + widget = self._widget + if touch.time_end != -1: + # `on_touch_up` might have been already fired so we need to find out it actually was or not. + tasks = await wait_any( + sleep(self._timeout), + event(widget, 'on_touch_up', filter=lambda w, t: t is touch), + ) + if tasks[0].finished: + raise MotionEventAlreadyEndedError(f"MotionEvent(uid={touch.uid}) has already ended") + self._no_cleanup = True + return self._always_false + step = (await current_task())._step + on_touch_up, on_touch_move = self._callbacks[not self._stop_dispatching] + touch.grab(widget) + self._uid_up = widget.fbind('on_touch_up', partial(on_touch_up, step, touch)) + self._uid_move = widget.fbind('on_touch_move', partial(on_touch_move, step, touch)) + assert self._uid_up + assert self._uid_move + return self._true_if_touch_move_false_if_touch_up + + async def __aexit__(self, *args): + if self._no_cleanup: + return + w = self._widget + self._touch.ungrab(w) + w.unbind_uid('on_touch_up', self._uid_up) + w.unbind_uid('on_touch_move', self._uid_move) + + +async def touch_up_event(widget, touch, *, stop_dispatching=False, timeout=1.) -> T.Awaitable: + ''' + *(experimental state)* + + Returns an awaitable that can be used to wait for the ``on_touch_up`` event of the given ``touch`` to occur. + + .. code-block:: + + __, touch = await event(widget, 'on_touch_down') + ... + await touch_up_event(widget, touch) + + You might wonder what the differences are compared to the code below. + + .. code-block:: + :emphasize-lines: 3 + + __, touch = await event(widget, 'on_touch_down') + ... + await event(widget, 'on_touch_up', filter=lambda w, t: t is touch) + + The latter has two problems: + If the ``on_touch_up`` event of the ``touch`` occurred before the highlighted line, + the execution will halt indefinitely at that point. + Even if the event didn't occur before that line, the execution still might halt because + `Kivy does not guarantee`_ that all touch events are delivered to all widgets. + + This API takes care of both problems in the same way as :func:`watch_touch`. + If the ``on_touch_up`` event has already occurred, it raises a :exc:`MotionEventAlreadyEndedError` exception. + And it grabs/ungrabs the ``touch`` so that it won't miss any touch events. + + Needless to say, if you want to wait for both ``on_touch_move`` and ``on_touch_up`` events at the same time, + use :func:`watch_touch` or :func:`rest_of_touch_events` instead. + + .. _Kivy does not guarantee: https://kivy.org/doc/stable/guide/inputs.html#grabbing-touch-events + ''' + touch.grab(widget) + try: + awaitable = event( + widget, 'on_touch_up', stop_dispatching=stop_dispatching, + filter=lambda w, t: t.grab_current is w and t is touch, + ) + if touch.time_end == -1: + await awaitable + else: + tasks = await wait_any(sleep(timeout), awaitable) + if tasks[0].finished: + raise MotionEventAlreadyEndedError(f"MotionEvent(uid={touch.uid}) has already ended") + finally: + touch.ungrab(widget) + + +async def rest_of_touch_events(widget, touch, *, stop_dispatching=False, timeout=1.) -> T.AsyncIterator[None]: + ''' + Returns an async iterator that iterates the number of times ``on_touch_move`` occurs, + and ends the iteration when ``on_touch_up`` occurs. + + .. code-block:: + + async for __ in rest_of_touch_events(widget, touch): + print('on_touch_move') + print('on_touch_up') + + This is a wrapper for :class:`watch_touch`. Although this one, I believe, is more intuitive than + :class:`watch_touch`, it has a couple of disadvantages - see :ref:`the-problem-with-async-generators`. + ''' + async with watch_touch(widget, touch, stop_dispatching=stop_dispatching, timeout=timeout) as in_progress: + while await in_progress(): + yield + + +rest_of_touch_moves = rest_of_touch_events diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/_utils.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/_utils.py new file mode 100644 index 0000000..0fb04f5 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/_utils.py @@ -0,0 +1,256 @@ +__all__ = ('transform', 'suppress_event', 'create_texture_from_text', 'sync_attr', 'sync_attrs', ) +import typing as T +from contextlib import contextmanager +from functools import partial + +from kivy.event import EventDispatcher +from kivy.graphics import PushMatrix, PopMatrix, InstructionGroup +from kivy.graphics.texture import Texture +from kivy.core.text import Label as CoreLabel +from kivy.core.text.markup import MarkupLabel as CoreMarkupLabel + + +@contextmanager +def transform(widget, *, use_outer_canvas=False) -> T.ContextManager[InstructionGroup]: + ''' + Returns a context manager that sandwiches the ``widget``'s existing canvas instructions between + a :class:`kivy.graphics.PushMatrix` and a :class:`kivy.graphics.PopMatrix`, and inserts an + :class:`kivy.graphics.InstructionGroup` right next to the ``PushMatrix``. Those three instructions will be removed + when the context manager exits. + + This may be useful when you want to animate a widget. + + **Usage** + + .. code-block:: + + from kivy.graphics import Rotate + + async def rotate_widget(widget, *, angle=360.): + with transform(widget) as ig: # <- InstructionGroup + ig.add(rotate := Rotate(origin=widget.center)) + await anim_attrs(rotate, angle=angle) + + If the position or size of the ``widget`` changes during the animation, you might need :class:`sync_attr`. + + **The use_outer_canvas parameter** + + While the context manager is active, the content of the widget's canvas would be: + + .. code-block:: yaml + + # ... represents existing instructions + + Widget: + canvas.before: + ... + canvas: + PushMatrix + InstructionGroup + ... + PopMatrix + canvas.after: + ... + + but if ``use_outer_canvas`` is True, it would be: + + .. code-block:: yaml + + Widget: + canvas.before: + PushMatrix + InstructionGroup + ... + canvas: + ... + canvas.after: + ... + PopMatrix + ''' + + c = widget.canvas + if use_outer_canvas: + before = c.before + after = c.after + push_mat_idx = 0 + ig_idx = 1 + else: + c.before # ensure 'canvas.before' exists + + # Index starts from 1 because 'canvas.before' is sitting at index 0 and we usually want it to remain first. + # See https://github.com/kivy/kivy/issues/7945 for details. + push_mat_idx = 1 + ig_idx = 2 + before = after = c + + push_mat = PushMatrix() + ig = InstructionGroup() + pop_mat = PopMatrix() + + before.insert(push_mat_idx, push_mat) + before.insert(ig_idx, ig) + after.add(pop_mat) + try: + yield ig + finally: + after.remove(pop_mat) + before.remove(ig) + before.remove(push_mat) + + +class suppress_event: + ''' + Returns a context manager that prevents the callback functions (including the default handler) bound to an event + from being called. + + .. code-block:: + :emphasize-lines: 4 + + from kivy.uix.button import Button + + btn = Button() + btn.bind(on_press=lambda __: print("pressed")) + with suppress_event(btn, 'on_press'): + btn.dispatch('on_press') + + The above code prints nothing because the callback function won't be called. + + Strictly speaking, this context manager doesn't prevent all callback functions from being called. + It only prevents the callback functions that were bound to an event before the context manager enters. + Thus, the following code prints ``pressed``. + + .. code-block:: + :emphasize-lines: 5 + + from kivy.uix.button import Button + + btn = Button() + with suppress_event(btn, 'on_press'): + btn.bind(on_press=lambda __: print("pressed")) + btn.dispatch('on_press') + + .. warning:: + + You need to be careful when you suppress an ``on_touch_xxx`` event. + See :ref:`kivys-event-system` for details. + ''' + __slots__ = ('_dispatcher', '_name', '_bind_uid', '_filter', ) + + def __init__(self, event_dispatcher, event_name, *, filter=lambda *args, **kwargs: True): + self._dispatcher = event_dispatcher + self._name = event_name + self._filter = filter + + def __enter__(self): + self._bind_uid = self._dispatcher.fbind(self._name, self._filter) + + def __exit__(self, *args): + self._dispatcher.unbind_uid(self._name, self._bind_uid) + + +def create_texture_from_text(*, markup=False, **label_kwargs) -> Texture: + ''' + .. code-block:: + + from kivy.metrics import sp + + texture = create_texture_from_text( + text='Hello', + font_size=sp(50), + font_name='Roboto', + color=(1, 0, 0, 1), + ) + + The keyword arguments are similar to :external:kivy:doc:`api-kivy.uix.label` 's. + ''' + core_cls = CoreMarkupLabel if markup else CoreLabel + core = core_cls(**label_kwargs) + core.refresh() + return core.texture + + +class sync_attr: + ''' + Returns a context manager that creates one-directional binding between attributes. + + .. code-block:: + + import types + + widget = Widget() + obj = types.SimpleNamespace() + + with sync_attr(from_=(widget, 'x'), to_=(obj, 'xx')): + widget.x = 10 + assert obj.xx == 10 # synchronized + obj.xx = 20 + assert widget.x == 10 # but not the other way around + + This can be particularly useful when combined with :func:`transform`. + + .. code-block:: + + from kivy.graphics import Rotate + + async def rotate_widget(widget, *, angle=360.): + with transform(widget) as ig: + ig.add(rotate := Rotate(origin=widget.center)) + with sync_attr(from_=(widget, 'center'), to_=(rotate, 'origin')): + await anim_attrs(rotate, angle=angle) + + .. versionadded:: 0.6.1 + ''' + __slots__ = ('_from', '_to', '_bind_uid', ) + + def __init__(self, from_: T.Tuple[EventDispatcher, str], to_: T.Tuple[T.Any, str]): + self._from = from_ + self._to = to_ + + def _sync(setattr, obj, attr_name, event_dispatcher, new_value): + setattr(obj, attr_name, new_value) + + def __enter__(self, partial=partial, sync=partial(_sync, setattr)): + self._bind_uid = self._from[0].fbind(self._from[1], partial(sync, *self._to)) + + def __exit__(self, *args): + self._from[0].unbind_uid(self._from[1], self._bind_uid) + + del _sync + + +class sync_attrs: + ''' + When multiple :class:`sync_attr` calls take the same ``from_`` argument, they can be merged into a single + :class:`sync_attrs` call. For instance, the following code: + + .. code-block:: + + with sync_attr((widget, 'x'), (obj1, 'x')), sync_attr((widget, 'x'), (obj2, 'xx')): + ... + + can be replaced with the following one: + + .. code-block:: + + with sync_attrs((widget, 'x'), (obj1, 'x'), (obj2, 'xx')): + ... + + .. versionadded:: 0.6.1 + ''' + __slots__ = ('_from', '_to', '_bind_uid', ) + + def __init__(self, from_: T.Tuple[EventDispatcher, str], *to_): + self._from = from_ + self._to = to_ + + def _sync(setattr, to_, event_dispatcher, new_value): + for obj, attr_name in to_: + setattr(obj, attr_name, new_value) + + def __enter__(self, partial=partial, sync=partial(_sync, setattr)): + self._bind_uid = self._from[0].fbind(self._from[1], partial(sync, self._to)) + + def __exit__(self, *args): + self._from[0].unbind_uid(self._from[1], self._bind_uid) + + del _sync diff --git a/kivy_venv/lib/python3.11/site-packages/asynckivy/vanim.py b/kivy_venv/lib/python3.11/site-packages/asynckivy/vanim.py new file mode 100644 index 0000000..d588a3e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/asynckivy/vanim.py @@ -0,0 +1,20 @@ +__all__ = ( + 'dt', 'delta_time', + 'et', 'elapsed_time', + 'dt_et', 'delta_time_elapsed_time', + 'progress', + 'dt_et_progress', 'delta_time_elapsed_time_progress', +) + +import warnings +from . import _anim_with_xxx + + +warnings.warn("The 'vanim' module is deprecated. Use 'asynckivy.anim_with_xxx' instead.") + + +delta_time = dt = _anim_with_xxx.anim_with_dt +elapsed_time = et = _anim_with_xxx.anim_with_et +progress = _anim_with_xxx.anim_with_ratio +delta_time_elapsed_time = dt_et = _anim_with_xxx.anim_with_dt_et +delta_time_elapsed_time_progress = dt_et_progress = _anim_with_xxx.anim_with_dt_et_ratio diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/RECORD b/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/RECORD deleted file mode 100644 index 1b88c37..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/RECORD +++ /dev/null @@ -1,394 +0,0 @@ -../../../bin/kivymd.add_view,sha256=u2N52U9NKr85se5a66gdG5Old498YxKwDezJ-cwIZAY,266 -../../../bin/kivymd.create_project,sha256=6LnjVohJ7V3MZYfVCtWqtyHHaku6Io-y0pN1BFPmDAM,272 -../../../bin/kivymd.make_release,sha256=hKmHIsjNRMSeSS3A2PNqeer_kEliqi6NhzI9bmLc0Zo,269 -kivymd-1.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -kivymd-1.2.0.dist-info/LICENSE,sha256=bWmhpIowwWDSSAQwBDm2S0_cUBhZc2yzQ0DwcT9-TrY,1691 -kivymd-1.2.0.dist-info/METADATA,sha256=GNIP7_ZragUNLOP6JBXj04nv3clgw33m1MlflQ8a9g4,15020 -kivymd-1.2.0.dist-info/RECORD,, -kivymd-1.2.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd-1.2.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91 -kivymd-1.2.0.dist-info/entry_points.txt,sha256=1-yhsSzIhBNRiKNTmevRDT02q8qzg3upZcWAyrr880w,342 -kivymd-1.2.0.dist-info/top_level.txt,sha256=ABq82F4nFM8sy3xAhxiO5XXT_lAen0YqiDD29qRiPhc,7 -kivymd/__init__.py,sha256=Lk1_FB7hhUcfOVc2hAVZdmpJa9RStEhpKf3Gx-Nm1C8,2158 -kivymd/__pycache__/__init__.cpython-311.pyc,, -kivymd/__pycache__/_version.cpython-311.pyc,, -kivymd/__pycache__/app.cpython-311.pyc,, -kivymd/__pycache__/color_definitions.cpython-311.pyc,, -kivymd/__pycache__/factory_registers.cpython-311.pyc,, -kivymd/__pycache__/font_definitions.cpython-311.pyc,, -kivymd/__pycache__/icon_definitions.cpython-311.pyc,, -kivymd/__pycache__/material_resources.cpython-311.pyc,, -kivymd/__pycache__/theming.cpython-311.pyc,, -kivymd/__pycache__/theming_dynamic_text.cpython-311.pyc,, -kivymd/_version.py,sha256=KnOtqZaS5l4zlFEKhhqjLzpQaqc4Kmdc5stnQiKUDYs,140 -kivymd/app.py,sha256=M1rwjNfNURnCdhVB_Rk7Hz5cljpwJxN8T6UhKkR4qaI,4222 -kivymd/color_definitions.py,sha256=vkOEWwRem3-Vh4dOIyJdSPrg88kyLaD6QZtpx7ecuV0,21550 -kivymd/effects/__init__.py,sha256=ySU96kLlmPF4QB-hDa0WM_Y0Odq1MPSR568yI_hiaqI,24 -kivymd/effects/__pycache__/__init__.cpython-311.pyc,, -kivymd/effects/fadingedge/__init__.py,sha256=ED7rP0TF96RxwjtLhj3KdAfqo7-YQuA4B10jnWuuMEg,41 -kivymd/effects/fadingedge/__pycache__/__init__.cpython-311.pyc,, -kivymd/effects/fadingedge/__pycache__/fadingedge.cpython-311.pyc,, -kivymd/effects/fadingedge/fadingedge.py,sha256=ZM3JRhQ7kvNuj8Z52VUq6MLF2sh7BEoa4_5oc7-mvcg,6054 -kivymd/effects/roulettescroll/__init__.py,sha256=Ep2YTxf03Y_A2GbosQIsm8AFsuOeE-w5VPL_NxckTbY,49 -kivymd/effects/roulettescroll/__pycache__/__init__.cpython-311.pyc,, -kivymd/effects/roulettescroll/__pycache__/roulettescroll.cpython-311.pyc,, -kivymd/effects/roulettescroll/roulettescroll.py,sha256=b4pJtyKDUqRzSGW4iIZpgUWV91QlRxgUXaPreBUVVEI,8092 -kivymd/effects/stiffscroll/__init__.py,sha256=tkGDtFz6s7W3iFtHRDo6NYWadL85cn7GXKAQQfpiJRk,43 -kivymd/effects/stiffscroll/__pycache__/__init__.cpython-311.pyc,, -kivymd/effects/stiffscroll/__pycache__/stiffscroll.cpython-311.pyc,, -kivymd/effects/stiffscroll/stiffscroll.py,sha256=3bSj43xOzhBcBSn58iLUiaNWevlHuO8seklAyfvZsJE,7117 -kivymd/factory_registers.py,sha256=0wC2kYMlYCNdShEzETUWEyLsESwwDgnGrgsvVL3t7-M,6431 -kivymd/font_definitions.py,sha256=2-4eWkHy4GUT_HhIr5GSEK7CUd71th6Mvi3QAPibkTA,1628 -kivymd/fonts/Roboto-Black.ttf,sha256=OHLps5dgobWaweGSYz27O1jllbTUI5MKx97VJemuJeA,171480 -kivymd/fonts/Roboto-BlackItalic.ttf,sha256=AT0ipPsmOK26KFVe4ZNm9FhfbcUzt8My9JMaIxSXyyI,177552 -kivymd/fonts/Roboto-Bold.ttf,sha256=fQuZHuPgvnrwGtfqjNK-6mwAol5nmgImtnN_B5qv_4Y,170760 -kivymd/fonts/Roboto-BoldItalic.ttf,sha256=pLysFPQZqX3gkXGYpLxRw-1PxKPbn2ilEC8jZk7gE1Q,174952 -kivymd/fonts/Roboto-Italic.ttf,sha256=X86Lb4up9NGfDVNeJB1WorjnK7B-ffcR2WjQku9_n8o,173932 -kivymd/fonts/Roboto-Light.ttf,sha256=ptND1CW8ONuQFS-gYFixxzkeypJk8zTvZcHOF1CFxvY,170420 -kivymd/fonts/Roboto-LightItalic.ttf,sha256=Nkoj4Txkk3w_zqPbd4YouJN5yJOEm7reblv4PF9gXGo,176616 -kivymd/fonts/Roboto-Medium.ttf,sha256=8gXMURgh6lYHihBVV_zqYlMSlATUEcmX4YZvvQBqu2g,172064 -kivymd/fonts/Roboto-MediumItalic.ttf,sha256=8Xfu0Q0kcNE-xo0EkHpYKCnQ7oKB-KAqkG9pVMeBblg,176864 -kivymd/fonts/Roboto-Regular.ttf,sha256=eehRQEZX2sIQaz0irSVtR4JKmldlRY7bcskQKkWBbZU,171676 -kivymd/fonts/Roboto-Thin.ttf,sha256=PZH3qmnLf3BkA1iVxWasXLmyCEWC01Gvcme7Tg-6YPU,171904 -kivymd/fonts/Roboto-ThinItalic.ttf,sha256=9eiaNEiUpg8PmPHAGCulH1BG4p3eMRiCDTkCVEWuqug,176300 -kivymd/fonts/materialdesignicons-webfont.ttf,sha256=pY7LVPRe7Br628ITFNHwkyzwCeXLx_MiXX5KThtx72s,1279992 -kivymd/icon_definitions.py,sha256=NPLejj2aOoR3N97zW8H8K2Sw7ECqYXQ_xLALkD0Yo-c,276638 -kivymd/images/alpha_layer.png,sha256=FRlE7o8oQebgftB_P9I-o-a6mslCr7aIrY3Zo6QGuAs,553 -kivymd/images/black.png,sha256=2oJF5FzBeTXDdA7lE2kQ_ftDv8TRtCOiTQtfvCf_qlM,147 -kivymd/images/blue.png,sha256=vOIOgEL_pAc4anbPSOhe6dxcEYi58dh3jlprNc36ASQ,147 -kivymd/images/folder.png,sha256=xY9uUYegh7hDzFI4iPEA-zyFBzq9IE007zaJGGY5JY0,2311 -kivymd/images/green.png,sha256=GK69rWfb7kTIlxD8nPjSq1X6k06SO1AxstcJLskJtp4,147 -kivymd/images/logo/kivymd-icon-128.png,sha256=VjWHaIJ-MSyP7ZnM25xgBfp_wG4vbQhr8B8QOTcXeaM,19826 -kivymd/images/logo/kivymd-icon-256.png,sha256=5iJVryvz0D-IvqLGTsaeJlYhZYk2R7OxskKiNgw25Ks,31943 -kivymd/images/logo/kivymd-icon-512.png,sha256=JAtfTEA7AWBPcMhFGqlwSQmQSq-lWAEJ8Y98qmDe0I0,54349 -kivymd/images/red.png,sha256=_4J3FtVS8IKWHCmr2e9GOy2cOGKmwV_0lzAbQf1IKLM,147 -kivymd/images/transparent.png,sha256=yM2bydAM4U0tnbs4zAuK2GnCC6I6NgAIfpw7rQhG1BQ,156 -kivymd/images/yellow.png,sha256=TH_ecR8dfP9R-8zYD4Ez24qZjaEbDTgJ0RcIcbHBIeE,147 -kivymd/material_resources.py,sha256=Ph4x6f5Lp7XOI4jqIkOBo_tZ_yMUPZDToO34A7akOlo,1763 -kivymd/theming.py,sha256=hQ5RfjoQlE8hWQKdQ2ZVOF_83ok-o8o0BzR-Kv5XDmA,58553 -kivymd/theming_dynamic_text.py,sha256=5TaO0Tc98EqK4-qTDMUO4FQHIU_wVxk6Y2GHhD6UcRk,2920 -kivymd/toast/__init__.py,sha256=1gkx40QSHU_0YprtsIJfog8Nnj6t_ZuXMLGhI7QZ0ww,238 -kivymd/toast/__pycache__/__init__.cpython-311.pyc,, -kivymd/toast/androidtoast/__init__.py,sha256=CCs95Gih3ri4OcO1J-xPFVmhGKPdY9idW3-V0mIjQ_s,226 -kivymd/toast/androidtoast/__pycache__/__init__.cpython-311.pyc,, -kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-311.pyc,, -kivymd/toast/androidtoast/androidtoast.py,sha256=cpbgUhZUjOU1go4Sn-qOo9h61Qhk4EAx1SFI9b4tC8o,2363 -kivymd/toast/kivytoast/__init__.py,sha256=mFY2Qi4xK_ttQxx7DnEg7XZxX68he0Hr09rZ60Pm95A,51 -kivymd/toast/kivytoast/__pycache__/__init__.cpython-311.pyc,, -kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-311.pyc,, -kivymd/toast/kivytoast/kivytoast.py,sha256=jRLOmiUV2Hf_09tTxnEfJUM5lEBZWfo_wEmgC9zLq5M,4135 -kivymd/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/tools/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/__pycache__/argument_parser.cpython-311.pyc,, -kivymd/tools/argument_parser.py,sha256=oxgJoS5WZH_ATDlaEU0HDtm7ve4DOJ23CnOILIBygy8,3193 -kivymd/tools/hotreload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/tools/hotreload/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/hotreload/__pycache__/app.cpython-311.pyc,, -kivymd/tools/hotreload/app.py,sha256=cJxyUxz1GbQ58pgPY3GZNSl-CoawoPUPoCUF3lECltI,16803 -kivymd/tools/packaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/tools/packaging/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/packaging/pyinstaller/__init__.py,sha256=ibpoURJJkyGuSgWhz8t-RuLnCmQf15TiFNr-HKTCPHA,1480 -kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-311.pyc,, -kivymd/tools/packaging/pyinstaller/hook-kivymd.py,sha256=cN0arGlcA_vLA8FTaAdzKgn7s2YQRCclv7G0e4c4o-Y,1053 -kivymd/tools/patterns/MVC/Model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/tools/patterns/MVC/Model/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/patterns/MVC/Model/__pycache__/database_firebase.cpython-311.pyc,, -kivymd/tools/patterns/MVC/Model/__pycache__/database_restdb.cpython-311.pyc,, -kivymd/tools/patterns/MVC/Model/database_firebase.py,sha256=5SlPAn-ROkh_ytnv2nteaVRAMC0MUSDM9u115Crfh-o,1376 -kivymd/tools/patterns/MVC/Model/database_restdb.py,sha256=Wc-zPffP-Wriyn-JGKCQrw3SGZMiAk-8LrVjParRtGM,3866 -kivymd/tools/patterns/MVC/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/tools/patterns/MVC/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/patterns/MVC/data/locales/po/en.po,sha256=1b2jIsEnyQi_T_gnpe5QeO90IdaQLwWClk5pn46ZjME,425 -kivymd/tools/patterns/MVC/data/locales/po/ru.po,sha256=mQd6ol2abec-PUJsrnRWNoah0din3IAg_F4gPf5VBVw,425 -kivymd/tools/patterns/MVC/libs/__init__.py,sha256=B6Kln9GZ6UrMOxr4lOnpTxGBMaoKlcar5wpyTGyUlIk,54 -kivymd/tools/patterns/MVC/libs/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/patterns/MVC/libs/__pycache__/translation.cpython-311.pyc,, -kivymd/tools/patterns/MVC/libs/translation.py,sha256=LiiMZUxVbAmUy71mVA-PyEkxwYrCAOcjeiDnlbCSPvQ,1411 -kivymd/tools/patterns/MVC/messages.pot,sha256=2cutqkylhtiY07tkXzE8yNqpux9eeoW3tdeoEbXdB1Y,582 -kivymd/tools/patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/tools/patterns/__pycache__/__init__.cpython-311.pyc,, -kivymd/tools/patterns/__pycache__/add_view.cpython-311.pyc,, -kivymd/tools/patterns/__pycache__/create_project.cpython-311.pyc,, -kivymd/tools/patterns/add_view.py,sha256=4J41yOosi9KkNrMBI6h6yJ_0LoncdJzmJDy-iShaArk,6576 -kivymd/tools/patterns/create_project.py,sha256=x3g37U3Dbk-ivUJ_aGgwvjpwBkyM4zIcPF3Bcyw1ASQ,39373 -kivymd/uix/__init__.py,sha256=iYE45157qMg3owm8X0BMVuStS8kU6bFyWr04VTjIPcw,2892 -kivymd/uix/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/__pycache__/anchorlayout.cpython-311.pyc,, -kivymd/uix/__pycache__/boxlayout.cpython-311.pyc,, -kivymd/uix/__pycache__/carousel.cpython-311.pyc,, -kivymd/uix/__pycache__/circularlayout.cpython-311.pyc,, -kivymd/uix/__pycache__/floatlayout.cpython-311.pyc,, -kivymd/uix/__pycache__/gridlayout.cpython-311.pyc,, -kivymd/uix/__pycache__/hero.cpython-311.pyc,, -kivymd/uix/__pycache__/recyclegridlayout.cpython-311.pyc,, -kivymd/uix/__pycache__/recycleview.cpython-311.pyc,, -kivymd/uix/__pycache__/relativelayout.cpython-311.pyc,, -kivymd/uix/__pycache__/responsivelayout.cpython-311.pyc,, -kivymd/uix/__pycache__/screen.cpython-311.pyc,, -kivymd/uix/__pycache__/screenmanager.cpython-311.pyc,, -kivymd/uix/__pycache__/scrollview.cpython-311.pyc,, -kivymd/uix/__pycache__/stacklayout.cpython-311.pyc,, -kivymd/uix/__pycache__/taptargetview.cpython-311.pyc,, -kivymd/uix/__pycache__/widget.cpython-311.pyc,, -kivymd/uix/anchorlayout.py,sha256=lrw9QiThjutmOTQix71atzJKP_cauoGUykiLdLfW99Q,1025 -kivymd/uix/backdrop/__init__.py,sha256=_9OLuWRMGEDqI1-ix6u0VJbuOpHJ6FLnJuODPYw2Eko,46 -kivymd/uix/backdrop/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/backdrop/__pycache__/backdrop.cpython-311.pyc,, -kivymd/uix/backdrop/backdrop.kv,sha256=L0w8al59AMQlkUrqjJsX1o7pMC9S70zt5KwulOON18g,1407 -kivymd/uix/backdrop/backdrop.py,sha256=eiJyqjY3hT-CFUVUX97f-2-npPxsr4TfsECjTGIrhTE,16311 -kivymd/uix/banner/__init__.py,sha256=JimxFPqzTx8l1pwI4rknb8r3QiDhLcI9xb4pIfpselo,42 -kivymd/uix/banner/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/banner/__pycache__/banner.cpython-311.pyc,, -kivymd/uix/banner/banner.kv,sha256=Mb5jq9VSLr0A4DqvTpSF9tKKh-JvpiN3oM9jKh8V5vs,1679 -kivymd/uix/banner/banner.py,sha256=E99gUMVE51jKNpTG3gyQKVAiaxQ4yeIznLzcGeOO4-A,12053 -kivymd/uix/behaviors/__init__.py,sha256=htl-U1oP2Tkmgul6gvkWV54LOzrTKe_jhwpT7dd372M,979 -kivymd/uix/behaviors/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/declarative_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/elevation.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/motion_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/rotate_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/scale_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/stencil_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-311.pyc,, -kivymd/uix/behaviors/backgroundcolor_behavior.py,sha256=a8Oa41plDW4G4tiIL-QNLc963VsW-5u7BuwvCTFfl3s,8962 -kivymd/uix/behaviors/declarative_behavior.py,sha256=MI2KelKtrkhXMIXoqu53ivHf_Zn7CQNQUtRHiwHpe9A,10236 -kivymd/uix/behaviors/elevation.py,sha256=cdQgXKUxM1-2YS1H3IM2yssc5FUQOJtptccMn7xQZvo,22211 -kivymd/uix/behaviors/focus_behavior.py,sha256=fhqtqiTMEopNMP7-_xWTVTtwiuKS9pkjnIIirjm3PcA,4240 -kivymd/uix/behaviors/hover_behavior.py,sha256=mhogwYWLTxJzqrO_Yk38v5BhYY7rFhVZ1xQfcEeXGq8,7616 -kivymd/uix/behaviors/magic_behavior.py,sha256=wwN40b_0NTG3Dopdhqt89PXYqY5mXSM4lY2xuIk1v9Q,4570 -kivymd/uix/behaviors/motion_behavior.py,sha256=15cwgX1UWlS2TJVBVOCia5FhYEOyr6PG9OPJRn_6aqU,7342 -kivymd/uix/behaviors/ripple_behavior.py,sha256=LoS_lIpYlzIGQWAX1FNh_56EmZ5NE_FsxNdQWaVvYZg,16658 -kivymd/uix/behaviors/rotate_behavior.py,sha256=Z8Ux_UwKZHoeu-cCl8hNc2EDVgC_hWni-ld8BpbL1uE,3268 -kivymd/uix/behaviors/scale_behavior.py,sha256=2ONpA7LKW5UhNI-ibBLlyQtOP_xyHDkasNxmqLO-z_c,4062 -kivymd/uix/behaviors/stencil_behavior.py,sha256=k6S2fwKMrxsFQKgI36q5-zCaQZ6LMFmv97jDPXD8e2Y,3017 -kivymd/uix/behaviors/toggle_behavior.py,sha256=wGi3INtIa_6chPcJMekye5jTaGkLMbwPtmW0aFdL_fE,8451 -kivymd/uix/behaviors/touch_behavior.py,sha256=uSP_AE8QuU_t_Q_Q-lUxuxh_Ms8TOZCewxUvSDsj04U,2475 -kivymd/uix/bottomnavigation/__init__.py,sha256=oAt66rNw5qp5ueAqLxfXs2iPpp68IWp5jVEnY3xMkwQ,85 -kivymd/uix/bottomnavigation/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/bottomnavigation/__pycache__/bottomnavigation.cpython-311.pyc,, -kivymd/uix/bottomnavigation/bottomnavigation.kv,sha256=7swDhIg8quCg7Vtm01jy1BnEtMx9qvMueFT88QBnkvU,4082 -kivymd/uix/bottomnavigation/bottomnavigation.py,sha256=BpVHD3XKnkZe2tLvNDn6Z23Kuq7l0lneY3TrRMK1pec,27138 -kivymd/uix/bottomsheet/__init__.py,sha256=VhYEno0toWnl6OmaxgYT7as1MIXYXLxhZHnTTrZ4TAY,255 -kivymd/uix/bottomsheet/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/bottomsheet/__pycache__/bottomsheet.cpython-311.pyc,, -kivymd/uix/bottomsheet/bottomsheet.kv,sha256=83LTxxGThfDnPN1ATAw4zU0_LB-jmERiFQyC8b3iedA,1047 -kivymd/uix/bottomsheet/bottomsheet.py,sha256=zv5Y3x7e3YdqnL_RJgMunpRrSd5BP4Oro7iO-QDLC7w,36674 -kivymd/uix/boxlayout.py,sha256=cWQuo2HJNnCoeY-HwXxsxZrv0YXR-IE4GGf8ymPVswI,1694 -kivymd/uix/button/__init__.py,sha256=hflz-R7RufS_l2SBqnwYtmAV5WAnsnmD2jwr3OQeusc,385 -kivymd/uix/button/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/button/__pycache__/button.cpython-311.pyc,, -kivymd/uix/button/button.kv,sha256=Z4AKWPAoRfPUflwHeUH81NpXSGZKrsiuSbAuy5GDTTA,6614 -kivymd/uix/button/button.py,sha256=O4u_5Ph7bVeicDk_fxc1HTiCzIZUr8rTGoyTIiPDnMU,75206 -kivymd/uix/card/__init__.py,sha256=DRguLhBrd_iil2DlVN7R8Euq711j6M81Hbkyfv9f4fs,130 -kivymd/uix/card/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/card/__pycache__/card.cpython-311.pyc,, -kivymd/uix/card/card.kv,sha256=dMijf_VPWw1Vc6cM7LdCf3KCFEcHtC9QnAU2icmAeXk,192 -kivymd/uix/card/card.py,sha256=cVm6dSVT6bUsWjyh2MbXUTPbCbgpGR7ekLWQb2afIJc,33665 -kivymd/uix/carousel.py,sha256=371UKLVsrndHekRbtAgnEtUU_ZQVDdzgXFJfhpJIr1c,7044 -kivymd/uix/chip/__init__.py,sha256=gpNRYAODC-ip0mjg36rcw8SMTQh_-223GPQsPPY65OE,50 -kivymd/uix/chip/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/chip/__pycache__/chip.cpython-311.pyc,, -kivymd/uix/chip/chip.kv,sha256=2ZUAugzJb3yw-Mf0rw7yKA07foheqB1gS9LhK0WQrZ4,1035 -kivymd/uix/chip/chip.py,sha256=a7xTg3jyQFhsJrmjBI2eVqBwhIJSY2UdjAufll0A75U,34934 -kivymd/uix/circularlayout.py,sha256=rG9QqC_1PoEqNhHCS8OK4oLcSvqBC2RSRZh9BHc8I0w,5506 -kivymd/uix/controllers/__init__.py,sha256=WNbi76mp899CTLeSZUnVJ0b5gSeUMJ9ganvmhEcNR40,231 -kivymd/uix/controllers/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/controllers/__pycache__/windowcontroller.cpython-311.pyc,, -kivymd/uix/controllers/windowcontroller.py,sha256=msVZQJwaekkIw4XUECXWAMOAsXw5NkFe38xIel1wG84,2335 -kivymd/uix/datatables/__init__.py,sha256=UQ_0tOrl5F-VSHQUpByyFw5LQzKTFAnWBNrUAnmPN2k,49 -kivymd/uix/datatables/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/datatables/__pycache__/datatables.cpython-311.pyc,, -kivymd/uix/datatables/datatables.kv,sha256=4u8JMT66faxazxGdVDAbHF3aT_ioNQRN04O_asr5Si0,6558 -kivymd/uix/datatables/datatables.py,sha256=OfoHMXPE1LYaNF03th3M8dMp_ZcgNV4wuQF0FTL5yc0,71271 -kivymd/uix/dialog/__init__.py,sha256=GcaS3dDIcvTSuJ9hYwNAsSKe89OKj1F7DEyu2j2HxCw,54 -kivymd/uix/dialog/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/dialog/__pycache__/dialog.cpython-311.pyc,, -kivymd/uix/dialog/dialog.kv,sha256=0afWO3x6OXEsOUHUSdxO7q3m6mOU7HlizEal4FCBCYQ,2155 -kivymd/uix/dialog/dialog.py,sha256=YjK1ozUeZ1HQpzSQvZQav0D3SWfavFJwB6g6vZlAaTs,21874 -kivymd/uix/dropdownitem/__init__.py,sha256=KyXd1v0gdOilE48z8Sp-Pqig_KJOdl0fyzWY8gPjA0U,54 -kivymd/uix/dropdownitem/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/dropdownitem/__pycache__/dropdownitem.cpython-311.pyc,, -kivymd/uix/dropdownitem/dropdownitem.kv,sha256=w46i9i807rDqqk1iZyBTqf2wzrt4jtRv2TKxyZaWxGI,906 -kivymd/uix/dropdownitem/dropdownitem.py,sha256=uVveaPSF27cjPCe8WiVp1zjfIIqU36e79NIUPhn193U,2633 -kivymd/uix/expansionpanel/__init__.py,sha256=fOxf-XTewfdiUEsKs4O8-ZFCLxG_zC12oWWIIGtO1Xc,182 -kivymd/uix/expansionpanel/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/expansionpanel/__pycache__/expansionpanel.cpython-311.pyc,, -kivymd/uix/expansionpanel/expansionpanel.kv,sha256=ImVy4Axm1-EqLYn52AR5gE6ypSE_jiDjQLz-FnrNZQM,355 -kivymd/uix/expansionpanel/expansionpanel.py,sha256=wPMCjQaN8HE1KwUd7-t97gI-xu-SxwZQAwrh4cRC55M,14229 -kivymd/uix/filemanager/__init__.py,sha256=TR0eAXbR-lzZUQLiVcGcVp5D1myeiLoXGpSWZitrWKI,52 -kivymd/uix/filemanager/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/filemanager/__pycache__/filemanager.cpython-311.pyc,, -kivymd/uix/filemanager/filemanager.kv,sha256=PCTjXk8I4yeZSwio4YwyXf2DkXDT0Uz92d0Bk16zKq8,2863 -kivymd/uix/filemanager/filemanager.py,sha256=k_NztZeWL75BPtCCf2e-cNuKgjiRlp6FRjWg9Z6ekKc,25313 -kivymd/uix/fitimage/__init__.py,sha256=BVf16byn839UfzikgsfJj_s5xH0PjGgU9gUuetb1dsU,44 -kivymd/uix/fitimage/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/fitimage/__pycache__/fitimage.cpython-311.pyc,, -kivymd/uix/fitimage/fitimage.py,sha256=o2RLSBlr9ahCeZZkmp1zyuhzPN24NKkSknWajBN1dhg,6516 -kivymd/uix/floatlayout.py,sha256=CUeL6e-k20Jg2zqWBL9sMvGzWgO7nNAsTd5So0UMgd4,1202 -kivymd/uix/gridlayout.py,sha256=HULtL-Zg0V_qN_7PZxe9Rr7jEuvGrnRA398tVZQSPkc,1671 -kivymd/uix/hero.py,sha256=P-rsRLF7OL5zHWpw6dgMRsWWWboXghyhFUw9GWpVu-I,17379 -kivymd/uix/imagelist/__init__.py,sha256=O4MQYEuq07GdwTytyXcE48Frp5Cy86zd8sqaYTPMFnk,47 -kivymd/uix/imagelist/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/imagelist/__pycache__/imagelist.cpython-311.pyc,, -kivymd/uix/imagelist/imagelist.kv,sha256=lRzJikpng24-TNQl2olHbgg4CUUKnwu8XEhaOY2ts1M,921 -kivymd/uix/imagelist/imagelist.py,sha256=DkSpHgpVgN1hHlFYQddWW-BxcxcuXCg90KEq0C-q3mU,8000 -kivymd/uix/label/__init__.py,sha256=DHLIPzLH5Uq0ChimIQIUVHQ42iAilC-F2xYzWqeqiXY,48 -kivymd/uix/label/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/label/__pycache__/label.cpython-311.pyc,, -kivymd/uix/label/label.kv,sha256=pQa0LHD7haztYEiOKnKLYiK4NbbKe_gwUQWrbPPDKb0,2055 -kivymd/uix/label/label.py,sha256=h_kFBoEXrzPlf9poja-lyLOTJVcgIq9kyPGnKu3lPXI,27892 -kivymd/uix/list/__init__.py,sha256=vaqucd89ij0ssXVPdvdl9bmsiXWbsUX7uZi-qT6QzIc,788 -kivymd/uix/list/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/list/__pycache__/list.cpython-311.pyc,, -kivymd/uix/list/list.kv,sha256=7tm7YcnXRURc_CHXRTYSw9zNObeviasG7arladTBZ5U,4715 -kivymd/uix/list/list.py,sha256=ymc8r18Eex4lNKAyp1UjbyWBFFKw-yaBe9WBy3dQDcs,45486 -kivymd/uix/menu/__init__.py,sha256=2a7cHJKvQmKiqu4_YWWQMF96owFFkRiMnEPX2R_kMbA,46 -kivymd/uix/menu/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/menu/__pycache__/menu.cpython-311.pyc,, -kivymd/uix/menu/menu.kv,sha256=ZJ3j-XggUkcEOsEdYRlMnsGit9jZQ1VvKICBgovy2DA,14002 -kivymd/uix/menu/menu.py,sha256=7dnA5hi2CXLWhlrRsBKj59J1FWthaBs2JneIdDzioTw,40950 -kivymd/uix/navigationdrawer/__init__.py,sha256=YFHzu07n8mPUmONaMYhwkx5OzK-J5mrzi109Mfj8S8o,240 -kivymd/uix/navigationdrawer/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/navigationdrawer/__pycache__/navigationdrawer.cpython-311.pyc,, -kivymd/uix/navigationdrawer/navigationdrawer.kv,sha256=Sh9d6kvvLONwvFSGJyXFGJ_hzsMhBekMNVnc4Kv5J9U,3586 -kivymd/uix/navigationdrawer/navigationdrawer.py,sha256=zg1m9K_Y2oYRgd5b2lvHAq5QxG5qc2Mzqpco0jTOcJA,46331 -kivymd/uix/navigationrail/__init__.py,sha256=4chGrBkw62foF0LYSBv7fkL9E5MvwUExi7-VG_cSbtM,155 -kivymd/uix/navigationrail/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/navigationrail/__pycache__/navigationrail.cpython-311.pyc,, -kivymd/uix/navigationrail/navigationrail.kv,sha256=xDomPL_lSc_ssaPUQsOfQizMZc5Gc3ZBFbAQO2dyN0o,5588 -kivymd/uix/navigationrail/navigationrail.py,sha256=Vr8PjkkbPkCjuvYAW_3Wd-Az9IxwOUWPSOACsQ3dz5k,42888 -kivymd/uix/pickers/__init__.py,sha256=7FvCnbWhjGmwOTqCMFrzTyPOa77Yytkrger5Vz7nOqI,152 -kivymd/uix/pickers/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/pickers/colorpicker/__init__.py,sha256=7HqXXTyARgnSu9I26V08PEj53OZw34MrOGmymOFOPJs,52 -kivymd/uix/pickers/colorpicker/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/pickers/colorpicker/__pycache__/colorpicker.cpython-311.pyc,, -kivymd/uix/pickers/colorpicker/colorpicker.kv,sha256=mbHuEnvYGd97CUJLfrnjZ0qCuJZ8SR14sj9ztUOI-yQ,8272 -kivymd/uix/pickers/colorpicker/colorpicker.py,sha256=ucG1wqZWrG2WBTcUp3kE3uzfVKNMGEMCI4BqOgvEg38,22794 -kivymd/uix/pickers/datepicker/__init__.py,sha256=-_u_4EUwtwFtCh5E58y7aMi4XCK4CJi0R5cZmDptDaE,107 -kivymd/uix/pickers/datepicker/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/pickers/datepicker/__pycache__/datepicker.cpython-311.pyc,, -kivymd/uix/pickers/datepicker/datepicker.kv,sha256=yk5zBwAZZJ_LOADiGBNLVNnx73Y-D58NoqCqooP0vss,14321 -kivymd/uix/pickers/datepicker/datepicker.py,sha256=MBsMiUwYOIumJnI_Dtmhu3UOESv1CjMDWhI84w0SCvw,50563 -kivymd/uix/pickers/timepicker/__init__.py,sha256=ZWB8oTmsgA1Uy5zv2I5QmwZ2N3spznS0anLvK-aytVQ,50 -kivymd/uix/pickers/timepicker/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/pickers/timepicker/__pycache__/timepicker.cpython-311.pyc,, -kivymd/uix/pickers/timepicker/timepicker.kv,sha256=1REiI_DBcT9868oWTAK1BmPhFn0LltmR9Lwq0EHVs-g,10618 -kivymd/uix/pickers/timepicker/timepicker.py,sha256=H3qUswLdaBQ63zbF19bt0y-WiHmCGUBNQt2Qh-_sUlY,25249 -kivymd/uix/progressbar/__init__.py,sha256=YLT-xcOX3Z_O8py_ENTH9wFIletQiMJ5vUVK6nr3Qco,52 -kivymd/uix/progressbar/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/progressbar/__pycache__/progressbar.cpython-311.pyc,, -kivymd/uix/progressbar/progressbar.kv,sha256=p0LzgpX6k_hd-r_CUTbQA2K2yO7M7SIyW8nx-0B8r4Q,1423 -kivymd/uix/progressbar/progressbar.py,sha256=5EYTXRxrV3JkCSidbmXRXt9FNt9tBF3gTTvGzOvh7cI,8523 -kivymd/uix/recyclegridlayout.py,sha256=gvtUVMG04EjsCw2wOPBWgGwu_anZS7buKjFAnZqVhyQ,1804 -kivymd/uix/recycleview.py,sha256=uBxnIU6FgjS44cE6K-Wqp4xel4tuV726_Vaa6W5f5tw,1008 -kivymd/uix/refreshlayout/__init__.py,sha256=r6yH7AYvn831mRtdu6KN_p4L42JBJh2qKpNIuHyckao,66 -kivymd/uix/refreshlayout/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/refreshlayout/__pycache__/refreshlayout.cpython-311.pyc,, -kivymd/uix/refreshlayout/refreshlayout.kv,sha256=5jMCt1kBIA91ll4rWNYZ33fheUvMxph3uUBAj0BhdH0,602 -kivymd/uix/refreshlayout/refreshlayout.py,sha256=lHL1nGOpHWiPqjD7r0dkSabhR6d_Qzis7xtuhpr8RjA,9638 -kivymd/uix/relativelayout.py,sha256=hYfETginxPuQvYU37w22E184wZD_A-eTfRgFw5SqwAE,1062 -kivymd/uix/responsivelayout.py,sha256=N1GMNejHlNssk8ZpC9kMuZELuWywUs9Dms_AzHcpU1Q,5409 -kivymd/uix/screen.py,sha256=WhRKHyC8o7t8c_lgBfX4_hpIz7L2kArKYSAVM7MA0cI,2280 -kivymd/uix/screenmanager.py,sha256=YD3pJ79LdINVEH2QS1zC-FpPQcg3m4mSZakYKh3a9Wc,5091 -kivymd/uix/scrollview.py,sha256=fKCNMqezyYpNlNrYXSueRQ7ISy1Qd6PD74INrPURvdE,948 -kivymd/uix/segmentedbutton/__init__.py,sha256=WpaigD2XLpcvKEEAqWoJTZiZRy3S4J47_KU36FhNPNc,96 -kivymd/uix/segmentedbutton/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/segmentedbutton/__pycache__/segmentedbutton.cpython-311.pyc,, -kivymd/uix/segmentedbutton/segmentedbutton.kv,sha256=1cE5UXZOI46aItAYYpM0cJdgWgPXc6dcqKMFG_0ZD2Y,840 -kivymd/uix/segmentedbutton/segmentedbutton.py,sha256=JjKLG0GO2AVyk-EJQwD8EBeKaaV3Rrk0tr_pkJRRs7Q,19122 -kivymd/uix/segmentedcontrol/__init__.py,sha256=z8ZZj8D5wDxyr__hqHZjscxexIw8CRDkYX6Dl9tZxxs,99 -kivymd/uix/segmentedcontrol/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/segmentedcontrol/__pycache__/segmentedcontrol.cpython-311.pyc,, -kivymd/uix/segmentedcontrol/segmentedcontrol.kv,sha256=jhZiaohChE-DqhZC-hA3_HHxJqQ08eEGmGSF7euyA4A,959 -kivymd/uix/segmentedcontrol/segmentedcontrol.py,sha256=AJQyaQN0et40snqGp9A1ag8HWRMEJqj5PeZ5hrit_sA,11084 -kivymd/uix/selection/__init__.py,sha256=12J_d15BRpSKymQbsMwSZ51Bo_5FauKwaY-d0z_2uAk,52 -kivymd/uix/selection/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/selection/__pycache__/selection.cpython-311.pyc,, -kivymd/uix/selection/selection.kv,sha256=lpA5YyksPcf2tJmmE754eOGsTRkNhLa0IGHAjmlXD70,379 -kivymd/uix/selection/selection.py,sha256=0j-12oo2dVzs-KZDJqHTLIIHDbLcQNerZzds-NFgq8Y,21378 -kivymd/uix/selectioncontrol/__init__.py,sha256=n1yw1X7ccriTWj1GPN-V9LvFXgxuFNfEy6uRHPyhQGA,71 -kivymd/uix/selectioncontrol/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/selectioncontrol/__pycache__/selectioncontrol.cpython-311.pyc,, -kivymd/uix/selectioncontrol/selectioncontrol.kv,sha256=4_Xlvgjpfi_3OmC2BPE7eZ_P4XRRGJw5AcmlXWsf1C0,4873 -kivymd/uix/selectioncontrol/selectioncontrol.py,sha256=Nu-eGtbmgjcaI8A4Ke7nleFOXqPnWuB4q0GTe7aZ4Yc,27724 -kivymd/uix/slider/__init__.py,sha256=TQwpwsmQElirZufuKGck-QZuYLe1V1Vd2HVUwSnPx8k,42 -kivymd/uix/slider/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/slider/__pycache__/slider.cpython-311.pyc,, -kivymd/uix/slider/slider.kv,sha256=ddb5p6jonpz3R9Lqg4tfKqKVTTy2s0_pkURnwUovmtA,6144 -kivymd/uix/slider/slider.py,sha256=nmYRtmX0OOnokQFr-xbWgomK6DnkoBjQXSfe6SjNcaU,8502 -kivymd/uix/sliverappbar/__init__.py,sha256=axuqy95RR0oDLXpBtH3EauoTZN56p6EAp3JFBC-MoYI,103 -kivymd/uix/sliverappbar/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/sliverappbar/__pycache__/sliverappbar.cpython-311.pyc,, -kivymd/uix/sliverappbar/sliverappbar.kv,sha256=_Ck4u11h0HJUQa4gNwEkCBR28xHspd6KNhZ-1oHPzac,1208 -kivymd/uix/sliverappbar/sliverappbar.py,sha256=ABfFDECbD9dbefONxHxKd6qFDhS-WNGOtlZE81r5VPs,14833 -kivymd/uix/snackbar/__init__.py,sha256=wG6Czix7Tb-3f69MAZqdxy2oY_-lQF8mYHQ_rueZ2lI,124 -kivymd/uix/snackbar/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/snackbar/__pycache__/snackbar.cpython-311.pyc,, -kivymd/uix/snackbar/snackbar.kv,sha256=M0HYjCKUR6-aSrleQaqVjXzrO2tbhCTgyuYTgn0s3S8,761 -kivymd/uix/snackbar/snackbar.py,sha256=9alYRqr9yxqFlMPHbuUrXCCcron2VOZiZSA_7hZz9gA,14432 -kivymd/uix/spinner/__init__.py,sha256=QI6bPGhsNGXXrXR-fPvEkbEfTUF3V3tY1wHTx_zZAJI,44 -kivymd/uix/spinner/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/spinner/__pycache__/spinner.cpython-311.pyc,, -kivymd/uix/spinner/spinner.kv,sha256=UerB-S-STJiBnjZw6HsRqAGCNxLw0bvfu5EegQQpzDQ,521 -kivymd/uix/spinner/spinner.py,sha256=PCRVUq07-ZP0ADAWSz5vATEZE9Blx62onzKc_AdOoNs,9121 -kivymd/uix/stacklayout.py,sha256=VE-OemocOjID3yVA6cwOt1KUn64NwdVCgVA1ssD1nAM,1724 -kivymd/uix/swiper/__init__.py,sha256=yjVyswA0HfoLRx9UW7k_GR497shvH5EthBbQk8CoU_4,43 -kivymd/uix/swiper/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/swiper/__pycache__/swiper.cpython-311.pyc,, -kivymd/uix/swiper/swiper.kv,sha256=iTaioAm-1WWmrGV4mqTizz3f4Qobv8WJZT-cwhEqyD0,291 -kivymd/uix/swiper/swiper.py,sha256=4Wwnj_jFvNhRlyPFvyXyPt01ZHeJt_25KcyjeZAB-II,14656 -kivymd/uix/tab/__init__.py,sha256=ui2pD_Y163wYI19O3JdSlVHhXUs83FHQUrZFi9FcGHs,62 -kivymd/uix/tab/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/tab/__pycache__/tab.cpython-311.pyc,, -kivymd/uix/tab/tab.kv,sha256=UuHLg4fS1CwROB4wwjWLxOmFk7xU5W78qWLZxSMMvSM,3809 -kivymd/uix/tab/tab.py,sha256=WnsmGmgjPWTYbtuNkOLRZSFRciGZwHOaaAWS_zHJaNM,67730 -kivymd/uix/taptargetview.py,sha256=8oIPH-HC-kxSOsKua0NJqeacb1oxjNIExPDFz46p3-k,26995 -kivymd/uix/templates/__init__.py,sha256=N58Ktx4cjBdiBsabyRkeJKOczHNQCGhWpQV_bGEy6CY,216 -kivymd/uix/templates/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/templates/rotatewidget/__init__.py,sha256=44Zf8a5sc5FSSfYX2RTWLAikbOzgi9g3Sm_LhroTsv4,39 -kivymd/uix/templates/rotatewidget/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/templates/rotatewidget/__pycache__/rotatewidget.cpython-311.pyc,, -kivymd/uix/templates/rotatewidget/rotatewidget.py,sha256=Ea9ImstSgTxTeNKikN7GQlu_tWcgjaAkE-iMJpuYAr4,764 -kivymd/uix/templates/scalewidget/__init__.py,sha256=QeR6z6AC4N1ylUnh0aI2cBAVDuOhsqUNF0Ux9Dza2zQ,37 -kivymd/uix/templates/scalewidget/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/templates/scalewidget/__pycache__/scalewidget.cpython-311.pyc,, -kivymd/uix/templates/scalewidget/scalewidget.py,sha256=xnV2r-bw3gwvhyiwWtueNkCODC7uTg2sSJCNwlTSWuA,804 -kivymd/uix/templates/stencilwidget/__init__.py,sha256=1hoHSMc_CudX4ixsz4dlvAPQi_ppki1ht1zi7oSza_g,41 -kivymd/uix/templates/stencilwidget/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/templates/stencilwidget/__pycache__/stencilwidget.cpython-311.pyc,, -kivymd/uix/templates/stencilwidget/stencilwidget.py,sha256=VbMTYBRJXH1BGh-1jxFIXLZVGOfVj47K-1DHCzCQG4U,843 -kivymd/uix/textfield/__init__.py,sha256=TsquVODAqOW9KRaVdZEwDSuj84xNu3btukztMO1u-R8,64 -kivymd/uix/textfield/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/textfield/__pycache__/textfield.cpython-311.pyc,, -kivymd/uix/textfield/textfield.kv,sha256=1YRvhffqhGvrTT6ak7-9Q8igtweGh3heUkudK5vAsog,11995 -kivymd/uix/textfield/textfield.py,sha256=ZgKSMcO0rPjbgbhZ9sKDtCEZ4UsQS9AlUhaJkwgS9fc,66314 -kivymd/uix/toolbar/__init__.py,sha256=ERxUgGcxjesHxwJoadRRiboL22K2gdMaIu1h57GJCcM,163 -kivymd/uix/toolbar/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/toolbar/__pycache__/toolbar.cpython-311.pyc,, -kivymd/uix/toolbar/toolbar.kv,sha256=XTLAQDaSN1lMW350XzY1Wy9gpIm-GVWQzuBhngsr2-g,3569 -kivymd/uix/toolbar/toolbar.py,sha256=qOySilG3qNjHwyFEKfcqWEzbTjeLLIEc0h1JwM9QwIU,67947 -kivymd/uix/tooltip/__init__.py,sha256=TrHnzd6U1iKu-TzPaBG4jPRL7Cdaa91FqQQiS37ce9o,64 -kivymd/uix/tooltip/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/tooltip/__pycache__/tooltip.cpython-311.pyc,, -kivymd/uix/tooltip/tooltip.kv,sha256=eiA1FwuWCUm8BTdTilYPS01V2lDoVh0mW-BkeEldGPY,1171 -kivymd/uix/tooltip/tooltip.py,sha256=_-crrT-DxKx12VGfCBqe3sV_nIPTmdklEDRGhngAl4k,10861 -kivymd/uix/transition/__init__.py,sha256=QA3XDj483c_iPrbiVHdkvVZhCKPu495dr5_6hBZRLgw,113 -kivymd/uix/transition/__pycache__/__init__.cpython-311.pyc,, -kivymd/uix/transition/__pycache__/transition.cpython-311.pyc,, -kivymd/uix/transition/transition.py,sha256=xwN5NcfogHwl4xeuzA1R49Vu88LBpuwmmv2N394UWV0,11349 -kivymd/uix/widget.py,sha256=6oQLzQuKgp4Rnulq63Zlw4fPjJ0RmkttrFx7NJbCMug,1074 -kivymd/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -kivymd/utils/__pycache__/__init__.cpython-311.pyc,, -kivymd/utils/__pycache__/asynckivy.cpython-311.pyc,, -kivymd/utils/__pycache__/fpsmonitor.cpython-311.pyc,, -kivymd/utils/__pycache__/set_bars_colors.cpython-311.pyc,, -kivymd/utils/asynckivy.py,sha256=veVrps6-mxT5JFgRpwzZ18_HFzx9CeG4IZomp8cTtyQ,1544 -kivymd/utils/fpsmonitor.py,sha256=QzWJs1lr9SIk2NDVeDUC2HRSyMXNXTWB39HvLDtL0vI,1024 -kivymd/utils/set_bars_colors.py,sha256=uhCQdrRhRW-P--5D5MJR6rcauOA8wmiFoapJJczkf14,4050 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/INSTALLER b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/LICENSE b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/LICENSE similarity index 65% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/LICENSE rename to kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/LICENSE index d096ac4..d96b44f 100755 --- a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/LICENSE +++ b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/LICENSE @@ -1,18 +1,7 @@ MIT License -Copyright (c) 2022 Andrés Rodríguez and other contributors - KivyMD library up to version 0.1.2 -Copyright (c) 2022 KivyMD Team and other contributors - KivyMD library version 0.1.3 and higher - -Other libraries used in the project: - -Copyright (c) 2010-2022 Kivy Team and other contributors -Copyright (c) 2022 Brian Knapp - Androidoast library -Copyright (c) 2022 LogicalDash - stiffscroll library -Copyright (c) 2022 Kivy Garden - tabs module -Copyright (c) 2022 Nattōsai Mitō - asynckivy module -Copyright (c) 2022 tshirtman - magic_behavior module -Copyright (c) 2022 shashi278 - taptargetview module -Copyright (c) 2022 Benedikt Zwölfer - fitimage module +Copyright (c) 2024 Andrés Rodríguez and other contributors - KivyMD library up to version 0.1.2 +Copyright (c) 2024 KivyMD Team and other contributors - KivyMD library version 0.1.3 and higher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/METADATA b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/METADATA similarity index 93% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/METADATA rename to kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/METADATA index 16bda35..2374a98 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/METADATA +++ b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: kivymd -Version: 1.2.0 +Version: 2.0.1.dev0 Summary: Set of widgets for Kivy inspired by Google's Material Design Home-page: https://github.com/kivymd/KivyMD Author: Andres Rodriguez, fork author - Ivanov Yuri @@ -40,8 +40,10 @@ Classifier: Topic :: Scientific/Engineering :: Visualization Requires-Python: >=3.7 Description-Content-Type: text/markdown; charset=UTF-8 License-File: LICENSE -Requires-Dist: kivy>=2.2.0 +Requires-Dist: kivy>=2.3.0 Requires-Dist: pillow +Requires-Dist: materialyoucolor>=2.0.7 +Requires-Dist: asynckivy<0.7,>=0.6 Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" Requires-Dist: black; extra == "dev" @@ -54,14 +56,14 @@ Requires-Dist: pytest-timeout; extra == "dev" Requires-Dist: coveralls; extra == "dev" Requires-Dist: pyinstaller[hook_testing]; extra == "dev" Provides-Extra: docs -Requires-Dist: sphinx; extra == "docs" -Requires-Dist: sphinx-autoapi==1.4.0; extra == "docs" -Requires-Dist: furo; extra == "docs" +Requires-Dist: sphinx==7.3.7; extra == "docs" +Requires-Dist: sphinx-autoapi==3.0.0; extra == "docs" +Requires-Dist: sphinx-book-theme; extra == "docs" Requires-Dist: sphinx-notfound-page; extra == "docs" Requires-Dist: sphinx-copybutton; extra == "docs" Requires-Dist: sphinx-tabs; extra == "docs" -# KivyMD [1.2.0](https://kivymd.readthedocs.io/en/latest/changelog/index.html) +# KivyMD [2.0.0](https://kivymd.readthedocs.io/en/latest/changelog/index.html) @@ -106,15 +108,16 @@ may be offered to you without asking too. ## Installation ```bash -pip install kivymd==1.2.0 +pip install kivymd==2.0.0 ``` ### Dependencies: -- [Kivy](https://github.com/kivy/kivy) >= 2.2.0 ([Installation](https://kivy.org/doc/stable/gettingstarted/installation.html)) +- [Kivy](https://github.com/kivy/kivy) >= 2.3.0 ([Installation](https://kivy.org/doc/stable/gettingstarted/installation.html)) - [Python 3.7+](https://www.python.org/) - [Pillow](https://github.com/python-pillow/Pillow/) -- [MaterialColor](https://github.com/T-Dynamos/materialyoucolor-pyhton) +- [MaterialColor](https://github.com/T-Dynamos/materialyoucolor-python) +- [asynckivy](https://github.com/asyncgui/asynckivy) ### How to install @@ -144,25 +147,16 @@ pip install . use a shallow clone (`git clone https://github.com/kivymd/KivyMD.git --depth 1`) to save time. If you need full commit history, then remove `--depth 1`. -### How to fix a shader bug on an android device - -Use `Kivy` from master branch or `Kivy` >= `2.2.0` and `KivyMD` (from master branch): - -```bash -pip install https://github.com/kivy/kivy/archive/master.zip -pip install https://github.com/kivymd/KivyMD/archive/master.zip -``` - -And use with `Buildozer`: - -```ini -requirements = kivy==master, https://github.com/kivymd/KivyMD/archive/master.zip -``` - ### How to use with [Buildozer](https://github.com/kivy/buildozer) ```ini -requirements = kivy==2.2.0, kivymd==1.2.0 +requirements = python3, + kivy, + https://github.com/kivymd/KivyMD/archive/master.zip, + materialyoucolor, + exceptiongroup, + asyncgui, + asynckivy ``` This will download latest release version of KivyMD from [PyPI](https://pypi.org/project/kivymd). @@ -171,7 +165,7 @@ If you want to use development version from [master](https://github.com/kivymd/K branch, you should specify link to zip archive: ```ini -requirements = kivy==2.1.0, https://github.com/kivymd/KivyMD/archive/master.zip +requirements = kivy, https://github.com/kivymd/KivyMD/archive/master.zip ``` Do not forget to run `buildozer android clean` or remove `.buildozer` directory @@ -356,6 +350,13 @@ and get your logo on our Readme with a link to your website. + +
+ Best Route Planner
+ Best Route Planner - Route Optimization Software +
+
+ #### Backers [Become a Backer](https://opencollective.com/kivymd/contribute/backer-16159) if you want to help develop this project. diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/RECORD b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/RECORD new file mode 100644 index 0000000..1557651 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/RECORD @@ -0,0 +1,332 @@ +../../../bin/kivymd.add_view,sha256=u2N52U9NKr85se5a66gdG5Old498YxKwDezJ-cwIZAY,266 +../../../bin/kivymd.create_project,sha256=6LnjVohJ7V3MZYfVCtWqtyHHaku6Io-y0pN1BFPmDAM,272 +../../../bin/kivymd.make_release,sha256=hKmHIsjNRMSeSS3A2PNqeer_kEliqi6NhzI9bmLc0Zo,269 +kivymd-2.0.1.dev0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +kivymd-2.0.1.dev0.dist-info/LICENSE,sha256=mAllwI3MAD8PljsyLcDu8ByZZYChrptzwCCA7pXK-70,1230 +kivymd-2.0.1.dev0.dist-info/METADATA,sha256=gFwEntEUBVZqXg9ZkEARN2wJJ3LRdRr49tfGGCKZt3c,15236 +kivymd-2.0.1.dev0.dist-info/RECORD,, +kivymd-2.0.1.dev0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd-2.0.1.dev0.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91 +kivymd-2.0.1.dev0.dist-info/direct_url.json,sha256=4K7_rCzsQAA6VPgFZkDLzO5m3GluAszqZihOmO2M4Lo,82 +kivymd-2.0.1.dev0.dist-info/entry_points.txt,sha256=1-yhsSzIhBNRiKNTmevRDT02q8qzg3upZcWAyrr880w,342 +kivymd-2.0.1.dev0.dist-info/top_level.txt,sha256=ABq82F4nFM8sy3xAhxiO5XXT_lAen0YqiDD29qRiPhc,7 +kivymd/__init__.py,sha256=d_a46HKOE97-WwIhBsv9yjAciPPx7yOLTeVzDynBVMI,1957 +kivymd/__pycache__/__init__.cpython-311.pyc,, +kivymd/__pycache__/_version.cpython-311.pyc,, +kivymd/__pycache__/animation.cpython-311.pyc,, +kivymd/__pycache__/app.cpython-311.pyc,, +kivymd/__pycache__/dynamic_color.cpython-311.pyc,, +kivymd/__pycache__/factory_registers.cpython-311.pyc,, +kivymd/__pycache__/font_definitions.cpython-311.pyc,, +kivymd/__pycache__/icon_definitions.cpython-311.pyc,, +kivymd/__pycache__/material_resources.cpython-311.pyc,, +kivymd/__pycache__/theming.cpython-311.pyc,, +kivymd/_version.py,sha256=6I20Wxx3c8MaIw5uF7UNh0yNERX7X5SkzUyMzzVsxno,115 +kivymd/animation.py,sha256=UMkBmLxjTlgz_tPVYrAGYxPIgVWAshAZivwlX_t983Q,6361 +kivymd/app.py,sha256=7As7g0jn_HnzRNDwPvulXGQtLd208x4Bm8dIeaRtEpE,4921 +kivymd/dynamic_color.py,sha256=jjlCogPmF5nmNHZzg9eG2YKZkoGtPUgaMb67RD5KGNo,16408 +kivymd/effects/__init__.py,sha256=ySU96kLlmPF4QB-hDa0WM_Y0Odq1MPSR568yI_hiaqI,24 +kivymd/effects/__pycache__/__init__.cpython-311.pyc,, +kivymd/effects/stiffscroll/__init__.py,sha256=tkGDtFz6s7W3iFtHRDo6NYWadL85cn7GXKAQQfpiJRk,43 +kivymd/effects/stiffscroll/__pycache__/__init__.cpython-311.pyc,, +kivymd/effects/stiffscroll/__pycache__/stiffscroll.cpython-311.pyc,, +kivymd/effects/stiffscroll/stiffscroll.py,sha256=3bSj43xOzhBcBSn58iLUiaNWevlHuO8seklAyfvZsJE,7117 +kivymd/factory_registers.py,sha256=MEdyWS0DPv8XnVbLEbygs_8pB0DHoNLPxLO56Qn9GX0,7775 +kivymd/font_definitions.py,sha256=4WLZWIPY_hdCbY3a-8mPhL-0pmxMmWvBbzN9MDqRo4E,3774 +kivymd/fonts/Roboto-Black.ttf,sha256=Ws4NCDOrg_8Y6pTkp3RfkZxFiuTqvCmCGCJt9CdczU0,168060 +kivymd/fonts/Roboto-BlackItalic.ttf,sha256=iy-H-wj_S11i51PHUP4ykYE1hlAfoU3SWLGtC_ulM64,174108 +kivymd/fonts/Roboto-Bold.ttf,sha256=7GhaRhBSlv5GyHRNpKEc-BGLpsEScZQXZvelRt9qp8c,167336 +kivymd/fonts/Roboto-BoldItalic.ttf,sha256=Yd9Zf3PJHyOMvoj-PFNnAtEg0Ei3myX6D094GP0SOm8,171508 +kivymd/fonts/Roboto-Italic.ttf,sha256=meSoUGETbpngUpKe0NheNjhPulw0t3MTmo9kM5xgmUM,170504 +kivymd/fonts/Roboto-Light.ttf,sha256=xXbFBkInG82__-0E-S3I1qmB2vMAkU0KIMilpaVwFcc,167000 +kivymd/fonts/Roboto-LightItalic.ttf,sha256=pIQGImhU00_plkKlJKQ7L9Jqfb7KgC1RAKipjSDhtz0,173172 +kivymd/fonts/Roboto-Medium.ttf,sha256=nQ1VowO_0Tt5qHch9lGF6T8jXi13_jmLLcpnrFGZFfU,168644 +kivymd/fonts/Roboto-MediumItalic.ttf,sha256=0_MXyX9AA4ksuWm8_OjePcnWf7-W_a-miy41lTF7DZQ,173416 +kivymd/fonts/Roboto-Regular.ttf,sha256=ThR6tkuf322J0B9rjDygs83cWdYIqOIhj5olBLXJjhQ,168260 +kivymd/fonts/Roboto-Thin.ttf,sha256=ZySPfoxu2zzn73Ow8ApTSn9CwRFs72POIbIDW16XmgY,168488 +kivymd/fonts/Roboto-ThinItalic.ttf,sha256=KM4lGpnNV9AKmCJaj8tmtgtp2vELCUfH7E7VA8Iq_-8,172860 +kivymd/fonts/materialdesignicons-webfont.ttf,sha256=YeirpaTpgf4iz3yOi82-oAR251xiw38Bv37jM2HWhCg,1307660 +kivymd/icon_definitions.py,sha256=DAc0bD1hlaFBeIiHo5p2_HFzZHr7IUhBXC3t3SBRffQ,282946 +kivymd/images/folder.png,sha256=xY9uUYegh7hDzFI4iPEA-zyFBzq9IE007zaJGGY5JY0,2311 +kivymd/images/logo/kivymd-icon-128.png,sha256=VjWHaIJ-MSyP7ZnM25xgBfp_wG4vbQhr8B8QOTcXeaM,19826 +kivymd/images/logo/kivymd-icon-256.png,sha256=5iJVryvz0D-IvqLGTsaeJlYhZYk2R7OxskKiNgw25Ks,31943 +kivymd/images/logo/kivymd-icon-512.png,sha256=JAtfTEA7AWBPcMhFGqlwSQmQSq-lWAEJ8Y98qmDe0I0,54349 +kivymd/images/transparent.png,sha256=yM2bydAM4U0tnbs4zAuK2GnCC6I6NgAIfpw7rQhG1BQ,156 +kivymd/material_resources.py,sha256=XnkSLVe-kTAEmEl9QzJRV-qs5sgmXjKT-V5GY30KXjY,576 +kivymd/theming.py,sha256=4dUslqibyGwEd9noplpmhGbH9daoGfdaqr-XlP55a-E,33018 +kivymd/toast/__init__.py,sha256=AWk597R4COWhbrfiUSjceBpmeaVer3-f3-L4dHOCQEY,54 +kivymd/toast/__pycache__/__init__.cpython-311.pyc,, +kivymd/toast/__pycache__/androidtoast.cpython-311.pyc,, +kivymd/toast/androidtoast.py,sha256=nPo0oay5MbILuwE36H4B_WtbMtAMSNtH89bCbZF0ULU,2361 +kivymd/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/tools/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/__pycache__/argument_parser.cpython-311.pyc,, +kivymd/tools/argument_parser.py,sha256=oxgJoS5WZH_ATDlaEU0HDtm7ve4DOJ23CnOILIBygy8,3193 +kivymd/tools/hotreload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/tools/hotreload/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/hotreload/__pycache__/app.cpython-311.pyc,, +kivymd/tools/hotreload/app.py,sha256=cJxyUxz1GbQ58pgPY3GZNSl-CoawoPUPoCUF3lECltI,16803 +kivymd/tools/packaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/tools/packaging/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/packaging/pyinstaller/__init__.py,sha256=ibpoURJJkyGuSgWhz8t-RuLnCmQf15TiFNr-HKTCPHA,1480 +kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-311.pyc,, +kivymd/tools/packaging/pyinstaller/hook-kivymd.py,sha256=cN0arGlcA_vLA8FTaAdzKgn7s2YQRCclv7G0e4c4o-Y,1053 +kivymd/tools/patterns/MVC/Model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/tools/patterns/MVC/Model/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/patterns/MVC/Model/__pycache__/database_firebase.cpython-311.pyc,, +kivymd/tools/patterns/MVC/Model/__pycache__/database_restdb.cpython-311.pyc,, +kivymd/tools/patterns/MVC/Model/database_firebase.py,sha256=5SlPAn-ROkh_ytnv2nteaVRAMC0MUSDM9u115Crfh-o,1376 +kivymd/tools/patterns/MVC/Model/database_restdb.py,sha256=Wc-zPffP-Wriyn-JGKCQrw3SGZMiAk-8LrVjParRtGM,3866 +kivymd/tools/patterns/MVC/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/tools/patterns/MVC/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/patterns/MVC/data/locales/po/en.po,sha256=1b2jIsEnyQi_T_gnpe5QeO90IdaQLwWClk5pn46ZjME,425 +kivymd/tools/patterns/MVC/data/locales/po/ru.po,sha256=mQd6ol2abec-PUJsrnRWNoah0din3IAg_F4gPf5VBVw,425 +kivymd/tools/patterns/MVC/libs/__init__.py,sha256=B6Kln9GZ6UrMOxr4lOnpTxGBMaoKlcar5wpyTGyUlIk,54 +kivymd/tools/patterns/MVC/libs/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/patterns/MVC/libs/__pycache__/translation.cpython-311.pyc,, +kivymd/tools/patterns/MVC/libs/translation.py,sha256=LiiMZUxVbAmUy71mVA-PyEkxwYrCAOcjeiDnlbCSPvQ,1411 +kivymd/tools/patterns/MVC/messages.pot,sha256=2cutqkylhtiY07tkXzE8yNqpux9eeoW3tdeoEbXdB1Y,582 +kivymd/tools/patterns/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/tools/patterns/__pycache__/__init__.cpython-311.pyc,, +kivymd/tools/patterns/__pycache__/add_view.cpython-311.pyc,, +kivymd/tools/patterns/__pycache__/create_project.cpython-311.pyc,, +kivymd/tools/patterns/add_view.py,sha256=4J41yOosi9KkNrMBI6h6yJ_0LoncdJzmJDy-iShaArk,6576 +kivymd/tools/patterns/create_project.py,sha256=x3g37U3Dbk-ivUJ_aGgwvjpwBkyM4zIcPF3Bcyw1ASQ,39373 +kivymd/uix/__init__.py,sha256=OP4CD9D1ezU3v3LGF0Yo91GIMIIJAUPMFSZNSLWdLOo,2793 +kivymd/uix/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/__pycache__/anchorlayout.cpython-311.pyc,, +kivymd/uix/__pycache__/boxlayout.cpython-311.pyc,, +kivymd/uix/__pycache__/circularlayout.cpython-311.pyc,, +kivymd/uix/__pycache__/floatlayout.cpython-311.pyc,, +kivymd/uix/__pycache__/gridlayout.cpython-311.pyc,, +kivymd/uix/__pycache__/hero.cpython-311.pyc,, +kivymd/uix/__pycache__/recyclegridlayout.cpython-311.pyc,, +kivymd/uix/__pycache__/recycleview.cpython-311.pyc,, +kivymd/uix/__pycache__/relativelayout.cpython-311.pyc,, +kivymd/uix/__pycache__/responsivelayout.cpython-311.pyc,, +kivymd/uix/__pycache__/screen.cpython-311.pyc,, +kivymd/uix/__pycache__/screenmanager.cpython-311.pyc,, +kivymd/uix/__pycache__/scrollview.cpython-311.pyc,, +kivymd/uix/__pycache__/stacklayout.cpython-311.pyc,, +kivymd/uix/__pycache__/widget.cpython-311.pyc,, +kivymd/uix/anchorlayout.py,sha256=K40IAwhpYiCemdh9jvOtTYF4IHnF3wdce3CjD6gXQy4,1365 +kivymd/uix/appbar/__init__.py,sha256=rL3GwJPgu7s0GcKDYri7sDtIznAYaniO2B_UxVEWU7A,264 +kivymd/uix/appbar/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/appbar/__pycache__/appbar.cpython-311.pyc,, +kivymd/uix/appbar/appbar.kv,sha256=EsSrZb-6aoYWc_kCZODw0EH6kzrTsbBESmRGv5ADzvU,2565 +kivymd/uix/appbar/appbar.py,sha256=6dquqLBAOLaBkxiwG7RQKAFF7t1OjHkBm8U83EGnSqg,38722 +kivymd/uix/badge/__init__.py,sha256=7CIBe8jXrxme7ankbSEmiPjT0EbJhPqawZHEcUa17mk,40 +kivymd/uix/badge/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/badge/__pycache__/badge.cpython-311.pyc,, +kivymd/uix/badge/badge.kv,sha256=LW70SjdLKfULFydUYceLZCLE3ulJdjUI9DLOe5zo-Ck,545 +kivymd/uix/badge/badge.py,sha256=3coq8IQq9St8t2lQOVA4gzu9ZEk3owPBCh4VdI-lnMk,1429 +kivymd/uix/behaviors/__init__.py,sha256=VWDGd99o9hPc4Ej4nuQmuVk8P3jRU4D2iQGj9h3zUyo,745 +kivymd/uix/behaviors/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/declarative_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/elevation.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/motion_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/rotate_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/scale_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/state_layer_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/stencil_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-311.pyc,, +kivymd/uix/behaviors/backgroundcolor_behavior.py,sha256=wtIo7A8wBr562wc9MliDk3vLkQGl8MzOEnWT6E-Jiqg,5275 +kivymd/uix/behaviors/declarative_behavior.py,sha256=E6vfUjq5QoVhm7pO5BKbWZrZjiLLq0wJ-lpKDi6mwgY,9776 +kivymd/uix/behaviors/elevation.py,sha256=ybmb_IQnpLv2gZZWrEhvp5hwNxDkapdZ9IfQFD7ciFI,20274 +kivymd/uix/behaviors/focus_behavior.py,sha256=lE84JzIeMGQyoRLYPt2z2OyaoNaJ6irKMUBhjzxKwa8,2865 +kivymd/uix/behaviors/hover_behavior.py,sha256=4Dslyh8758uViHlpbJOWzOqF0I-XEdrfO-NbWbFxZ-k,7745 +kivymd/uix/behaviors/magic_behavior.py,sha256=wwN40b_0NTG3Dopdhqt89PXYqY5mXSM4lY2xuIk1v9Q,4570 +kivymd/uix/behaviors/motion_behavior.py,sha256=Jl7v9KM19gRjXQ5K-stwtj89udxn20pKRSbYjnFd1ek,13820 +kivymd/uix/behaviors/ripple_behavior.py,sha256=o2Nl94jjd7HhH0b5kwEQcby1IAZT-gcjVaYTX6Vs4yg,16973 +kivymd/uix/behaviors/rotate_behavior.py,sha256=Z8Ux_UwKZHoeu-cCl8hNc2EDVgC_hWni-ld8BpbL1uE,3268 +kivymd/uix/behaviors/scale_behavior.py,sha256=2ONpA7LKW5UhNI-ibBLlyQtOP_xyHDkasNxmqLO-z_c,4062 +kivymd/uix/behaviors/state_layer_behavior.py,sha256=vr_ZNA8ggVcqDIjNDHgsa66HqlzoO4BgNOQeF4CCrLI,22138 +kivymd/uix/behaviors/stencil_behavior.py,sha256=yJtQY5Te-sa635lhz0pRwGGfyJeG5NtuzKpw3x1i8tw,3015 +kivymd/uix/behaviors/toggle_behavior.py,sha256=9dcZhoFTDMYNEMY8vNn0_Ei9VMLzy1aHyu26RcAOxEs,8644 +kivymd/uix/behaviors/touch_behavior.py,sha256=tOxMS2XyX-8o9romBRSqJJ-G0_Kp1P7mDXyylOC1t1U,2776 +kivymd/uix/bottomsheet/__init__.py,sha256=_pvnNLykM_K6xdruglPD6BbAmq7jo2uXfLarsoehxeo,158 +kivymd/uix/bottomsheet/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/bottomsheet/__pycache__/bottomsheet.cpython-311.pyc,, +kivymd/uix/bottomsheet/bottomsheet.kv,sha256=bemuxX5ABIiWP1v_dddvTEGaVwCJO6tOB6xSlgSj_jU,1163 +kivymd/uix/bottomsheet/bottomsheet.py,sha256=NBm4KJe4zPbdYHZ78jgxUj2YCjrJExd0Mdmq9sYys2E,12793 +kivymd/uix/boxlayout.py,sha256=KKONYwmEON79f2c6TLCxN4KNDj7E9yvuXs2oT5vEiMc,2031 +kivymd/uix/button/__init__.py,sha256=YhX34jdEnmi9kDofnQXg5v3H1NJWC_uX10f5fOmMFuo,239 +kivymd/uix/button/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/button/__pycache__/button.cpython-311.pyc,, +kivymd/uix/button/button.kv,sha256=0dJtZ_6IzOXbmHsBCDG64-OawofWV1okG1-eWP5nwQk,13278 +kivymd/uix/button/button.py,sha256=D7Rg3a4nT6zEJIZ6QONTigHfukAyQX8Ly1CuZ6tVINU,32731 +kivymd/uix/card/__init__.py,sha256=hjAO06DlaWnFP_wYQC5ZFMR4lobKDY0aztACZCvWgd8,113 +kivymd/uix/card/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/card/__pycache__/card.cpython-311.pyc,, +kivymd/uix/card/card.kv,sha256=DZMWfl8xWTrWaZ35miD23vBNtl5g_86QY3tv3mMyGdo,2926 +kivymd/uix/card/card.py,sha256=vdewoU57GGQDC5JjPsd3Z8Xy5aCKRtX_sEpYbpU7kig,27567 +kivymd/uix/chip/__init__.py,sha256=1uvEh6w800aZE4KV-LA6xch01o8Netw-h8N3Wy4Jb6E,135 +kivymd/uix/chip/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/chip/__pycache__/chip.cpython-311.pyc,, +kivymd/uix/chip/chip.kv,sha256=WUigEnF_XgGmwpv-5OUxaMY8LRZeXWxD4Ew36tk0JHI,3445 +kivymd/uix/chip/chip.py,sha256=n5-qQWzqXr6-eTGavpU7apsgX7WdsSnAP2MhKQ_6DyA,35420 +kivymd/uix/circularlayout.py,sha256=S5Z4R2Z7ENwSoDB-UqWjOl0lCoemTessK_C4KT3jru8,5407 +kivymd/uix/controllers/__init__.py,sha256=WNbi76mp899CTLeSZUnVJ0b5gSeUMJ9ganvmhEcNR40,231 +kivymd/uix/controllers/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/controllers/__pycache__/windowcontroller.cpython-311.pyc,, +kivymd/uix/controllers/windowcontroller.py,sha256=msVZQJwaekkIw4XUECXWAMOAsXw5NkFe38xIel1wG84,2335 +kivymd/uix/dialog/__init__.py,sha256=6dLZqaNu832pAEUyZBrEU6BdQ46SSOx9LI8jOMsnHD4,182 +kivymd/uix/dialog/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/dialog/__pycache__/dialog.cpython-311.pyc,, +kivymd/uix/dialog/dialog.kv,sha256=cLK7ZK50QwBmT73D3WgjnGbGGQ0BLrkQ8VHYmwFLFVA,2958 +kivymd/uix/dialog/dialog.py,sha256=iAr3ur0C0afToFtVXYSVpJrswIn6LX5_0IHZXaiobQI,18598 +kivymd/uix/divider/__init__.py,sha256=p52N9PonSJQ90tycWEJRDRpk8-D5R-ClBX0NN3xycl0,44 +kivymd/uix/divider/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/divider/__pycache__/divider.cpython-311.pyc,, +kivymd/uix/divider/divider.kv,sha256=hn-zA8BMGFadxbyojHmC07pvge-TLSxF0VCv2sMPizE,250 +kivymd/uix/divider/divider.py,sha256=GRPGVGjSQEaIefqehWO5untZom69GD-OgIAaGd7srhY,3702 +kivymd/uix/dropdownitem/__init__.py,sha256=e4eaF7uU5hU119DKcnTkj2hC-DtQoJ5gpx4n-EilUvI,74 +kivymd/uix/dropdownitem/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/dropdownitem/__pycache__/dropdownitem.cpython-311.pyc,, +kivymd/uix/dropdownitem/dropdownitem.kv,sha256=7ZljjTLoW__Fixl4jPTJG96upstMIfRxijwFLmbsWvc,1180 +kivymd/uix/dropdownitem/dropdownitem.py,sha256=6uneYSSjErpJSYaFnjGMNsOodt8c0VIs8OlZyKzRZkg,4773 +kivymd/uix/expansionpanel/__init__.py,sha256=OFJC5zK9iAI9doEVtptYDVPRuCQ2WTqd8LjScXQetEA,123 +kivymd/uix/expansionpanel/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/expansionpanel/__pycache__/expansionpanel.cpython-311.pyc,, +kivymd/uix/expansionpanel/expansionpanel.kv,sha256=vK3HR_n4ihGzD-lRSV5Y_RKRdk5a151YjUr7QlClK78,279 +kivymd/uix/expansionpanel/expansionpanel.py,sha256=9KwYGUFkpZGdLrnHZ2YsVovDzDE9vwqRlHnffRT8dzo,14722 +kivymd/uix/filemanager/__init__.py,sha256=TR0eAXbR-lzZUQLiVcGcVp5D1myeiLoXGpSWZitrWKI,52 +kivymd/uix/filemanager/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/filemanager/__pycache__/filemanager.cpython-311.pyc,, +kivymd/uix/filemanager/filemanager.kv,sha256=chfHadHr34MRk_UAuqPv9MOWLFhV_Ne84XfqL-NQF0I,2550 +kivymd/uix/filemanager/filemanager.py,sha256=Bsd_iFa2Yk_1JsW6rgobOOV2jOZDORrV5d5Y8IO9FWY,25902 +kivymd/uix/fitimage/__init__.py,sha256=BVf16byn839UfzikgsfJj_s5xH0PjGgU9gUuetb1dsU,44 +kivymd/uix/fitimage/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/fitimage/__pycache__/fitimage.cpython-311.pyc,, +kivymd/uix/fitimage/fitimage.py,sha256=r364DXB21psGT2Mw8MIDZtXYgTbujb0ya6b5t5PD1jA,3250 +kivymd/uix/floatlayout.py,sha256=QfF_RcIiG7ITlKfxvsmcW0gy97fXdk5BmVVyvAqibpg,1541 +kivymd/uix/gridlayout.py,sha256=HJCj1dEvIXwJj0tOK9lWNgovFYMMUAmtf5-SZO9__f8,2013 +kivymd/uix/hero.py,sha256=Pkdo1qCbwQnErS8zExJBOO23pM2TM56UaKgaftGwCq8,18420 +kivymd/uix/imagelist/__init__.py,sha256=VgCXsVvUFA9yfBpB3cEwLWqQR0_s6um2SMohe0wTcv4,111 +kivymd/uix/imagelist/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/imagelist/__pycache__/imagelist.cpython-311.pyc,, +kivymd/uix/imagelist/imagelist.kv,sha256=ukH2JEHvszq165W2Joylu7HoVl3pKYqMd7xhk_YWR54,1052 +kivymd/uix/imagelist/imagelist.py,sha256=hfJphayTAaVMQ-2BQsq5I3u8Tvyzm4ka_yNfNmAlRBg,7010 +kivymd/uix/label/__init__.py,sha256=DHLIPzLH5Uq0ChimIQIUVHQ42iAilC-F2xYzWqeqiXY,48 +kivymd/uix/label/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/label/__pycache__/label.cpython-311.pyc,, +kivymd/uix/label/label.kv,sha256=wCK0divTBiNdEqsDlxLAWStH69MYIDiLqiVZw9wg9fA,1323 +kivymd/uix/label/label.py,sha256=cQVTn_ArbRQsoPXMq-tgDP99ebU05oF4fILP-TQZPoc,26088 +kivymd/uix/list/__init__.py,sha256=JQbupFYB2E24FxZaz4y94ImASKQfS1eGrZPdfEpJ4-E,364 +kivymd/uix/list/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/list/__pycache__/list.cpython-311.pyc,, +kivymd/uix/list/list.kv,sha256=T7A3DeE6Vcsba2UacXMPPck2OBo2Kxzp7OOWPt1GjoI,4563 +kivymd/uix/list/list.py,sha256=koECDdLVSW1gluCNYT-VEST8N3fV6Y-jkAenKQ8fWXs,16560 +kivymd/uix/menu/__init__.py,sha256=2a7cHJKvQmKiqu4_YWWQMF96owFFkRiMnEPX2R_kMbA,46 +kivymd/uix/menu/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/menu/__pycache__/menu.cpython-311.pyc,, +kivymd/uix/menu/menu.kv,sha256=f35OBRCv2GiFoIoQsmJiD5OO6HJBMtWvtGFG8l7Njks,13637 +kivymd/uix/menu/menu.py,sha256=s5ykbqIKmZokiYRCbNsPImOrPxulVxuQnmOxEjl-sr8,40145 +kivymd/uix/navigationbar/__init__.py,sha256=a30ldn9-lWqS5o8UjCD2T2bUF95jRiC-yKF_r3KqXOs,139 +kivymd/uix/navigationbar/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/navigationbar/__pycache__/navigationbar.cpython-311.pyc,, +kivymd/uix/navigationbar/navigationbar.kv,sha256=-yaGGm2VOpgyAhvTtRNQD5rVKRuga9ZaADtbPgj-9SA,4035 +kivymd/uix/navigationbar/navigationbar.py,sha256=NCULciX76Vbe6DkQnWXhcDmRb8zvoaMGpdt9h07CgSQ,17122 +kivymd/uix/navigationdrawer/__init__.py,sha256=JNRnAbxuu0x2Xb8oO7lwBjKwKnYn2grJo_RZBcvBEIA,351 +kivymd/uix/navigationdrawer/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/navigationdrawer/__pycache__/navigationdrawer.cpython-311.pyc,, +kivymd/uix/navigationdrawer/navigationdrawer.kv,sha256=R6xE6aRpthfbeEfUm62pvnWRBA7uE-Za_mNa--H9JpA,2031 +kivymd/uix/navigationdrawer/navigationdrawer.py,sha256=gxkX3v9U5LCQXZLRPjcXJLoz_bUd-fTbP-M2H2qQCGM,38521 +kivymd/uix/navigationrail/__init__.py,sha256=1fQMqMVg-A3g6ZOFOrX-adJd8k9R7Xe5kijzq_U3Ksw,216 +kivymd/uix/navigationrail/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/navigationrail/__pycache__/navigationrail.cpython-311.pyc,, +kivymd/uix/navigationrail/navigationrail.kv,sha256=fMPYmHmNdS3WnSl2zpaxSuQMbLwg01YRU_3MbTh5eMs,2935 +kivymd/uix/navigationrail/navigationrail.py,sha256=MDMM8pdO5oGvH1gVGG4T2sLsz9lIWnrfR1C4qQe6Zsw,22302 +kivymd/uix/pickers/__init__.py,sha256=mhnmC4YAXu71eD90YcUe_l9kuMXUjHKALTFG6GdvtZM,242 +kivymd/uix/pickers/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/pickers/datepicker/__init__.py,sha256=gXQyrdPWJP9UxOwZ8VWuXZn8FB6S2AYnhl0FmAhI8Pc,116 +kivymd/uix/pickers/datepicker/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/pickers/datepicker/__pycache__/datepicker.cpython-311.pyc,, +kivymd/uix/pickers/datepicker/datepicker.kv,sha256=ZyWURj3ZzJ1Vb0-Fr4nI33sJQSLS2gAsjanoVHYBD5I,11736 +kivymd/uix/pickers/datepicker/datepicker.py,sha256=bYMdgC533VJdkR9vFVHM8mkyUyOrnUwbi7YvywR4hIs,60382 +kivymd/uix/pickers/timepicker/__init__.py,sha256=SBtSeGRb0VrrQgzFUGZxl0tQRwmUjKw5jpLhg371tNc,126 +kivymd/uix/pickers/timepicker/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/pickers/timepicker/__pycache__/timepicker.cpython-311.pyc,, +kivymd/uix/pickers/timepicker/timepicker.kv,sha256=xXlxSukPBj9L05B2hP-IdxEAvI3_O0hmuMQttlyCWGc,11334 +kivymd/uix/pickers/timepicker/timepicker.py,sha256=k6kbkVFug5fY_IN98j9YGWtewSsztqIVYa8zbQo51wY,38753 +kivymd/uix/progressindicator/__init__.py,sha256=dA8bwUMDtdx1CglUx3R73Xz6j9uUiiZT6qVphrx9me8,112 +kivymd/uix/progressindicator/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/progressindicator/__pycache__/progressindicator.cpython-311.pyc,, +kivymd/uix/progressindicator/progressindicator.kv,sha256=3XXvaGnEXAYpAFgQGE8gR3VGnYNYHapx827RBFtViDE,2221 +kivymd/uix/progressindicator/progressindicator.py,sha256=nuzNdqp1yGaQ6R80OQlYgXiYl42IPIRC2lRT9s1bwjw,18195 +kivymd/uix/recyclegridlayout.py,sha256=I399c4gsmyHAedTU1Biwd7QRYG60QJ-gx33WVct7CCc,2140 +kivymd/uix/recycleview.py,sha256=FVhr4SI0wjbQy08q68nDjh-Mqsdg6nfjwONZfHTBctY,1348 +kivymd/uix/refreshlayout/__init__.py,sha256=r6yH7AYvn831mRtdu6KN_p4L42JBJh2qKpNIuHyckao,66 +kivymd/uix/refreshlayout/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/refreshlayout/__pycache__/refreshlayout.cpython-311.pyc,, +kivymd/uix/refreshlayout/refreshlayout.kv,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/uix/refreshlayout/refreshlayout.py,sha256=lHL1nGOpHWiPqjD7r0dkSabhR6d_Qzis7xtuhpr8RjA,9638 +kivymd/uix/relativelayout.py,sha256=9ewM64QYa1PI_-yBWo5d6mY5PfWudJ5gYu94q9OYEm8,1434 +kivymd/uix/responsivelayout.py,sha256=N1GMNejHlNssk8ZpC9kMuZELuWywUs9Dms_AzHcpU1Q,5409 +kivymd/uix/screen.py,sha256=MgpjK_06QR66Zr4qdxfyiW3ACcaRHclBu-vGsep9Xuo,2625 +kivymd/uix/screenmanager.py,sha256=gSccPZXnmadWd6ueyl9jXXZK4vpUNgEaw0iHVBd262s,5549 +kivymd/uix/scrollview.py,sha256=jeE7XEi8AIigV_BAL-uUOzACu1hEwrTg0FrpabexNok,10744 +kivymd/uix/segmentedbutton/__init__.py,sha256=4xtmjrtfPcihaUtek7R5GXm6ltBYdyY6vz7UokAM1hY,147 +kivymd/uix/segmentedbutton/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/segmentedbutton/__pycache__/segmentedbutton.cpython-311.pyc,, +kivymd/uix/segmentedbutton/segmentedbutton.kv,sha256=JjfYB1naUJjHn8_quYhjnA8rgHH552eeoCLJb6JegRs,3468 +kivymd/uix/segmentedbutton/segmentedbutton.py,sha256=WaZ5DBY-7bmKdoclIl6W7q_ZN7ZgmKnPcJ75TbKiwng,22680 +kivymd/uix/selectioncontrol/__init__.py,sha256=n1yw1X7ccriTWj1GPN-V9LvFXgxuFNfEy6uRHPyhQGA,71 +kivymd/uix/selectioncontrol/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/selectioncontrol/__pycache__/selectioncontrol.cpython-311.pyc,, +kivymd/uix/selectioncontrol/selectioncontrol.kv,sha256=RylxUp5CUsYur5Md_kzX76tcy93rwbfcrOr_eMy9HuQ,5311 +kivymd/uix/selectioncontrol/selectioncontrol.py,sha256=mIgPK-LLcm4We5LfJ8vv2Ue6uuLQ-biiy0EkN5ZXJQQ,24706 +kivymd/uix/slider/__init__.py,sha256=f2N0xMmmO7e9aNdxn0vDqKRgpMIV8EScrsRl71OG1yU,78 +kivymd/uix/slider/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/slider/__pycache__/slider.cpython-311.pyc,, +kivymd/uix/slider/slider.kv,sha256=ydvIp5T2Htrh7Q2YMb2joStz8FVKENTnREmh6FcdVuk,7066 +kivymd/uix/slider/slider.py,sha256=DuuUi_jEpU8c5cyNmjZ5ZeKV4wU-sqNuyicWDOW8f28,17486 +kivymd/uix/sliverappbar/__init__.py,sha256=axuqy95RR0oDLXpBtH3EauoTZN56p6EAp3JFBC-MoYI,103 +kivymd/uix/sliverappbar/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/sliverappbar/__pycache__/sliverappbar.cpython-311.pyc,, +kivymd/uix/sliverappbar/sliverappbar.kv,sha256=jcGxg6SQqadnGHJVJ6S1U6MmwDwALuJ1dGV6Ra651YU,1370 +kivymd/uix/sliverappbar/sliverappbar.py,sha256=Usg3jZdwnaUwO-f2YycHVl9qJIkB29QLtURi0W2_OuA,11439 +kivymd/uix/snackbar/__init__.py,sha256=wP0ppgJbwiGlR3HEMdTo5Umbkq5xeCMkmmJI8ohv8lU,223 +kivymd/uix/snackbar/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/snackbar/__pycache__/snackbar.cpython-311.pyc,, +kivymd/uix/snackbar/snackbar.kv,sha256=JSszxvKOLOGvX3KR5DRekgPVRx8yqEq9BXwgRhbeqno,1836 +kivymd/uix/snackbar/snackbar.py,sha256=Li_UxO9IkrAf29Rkq1vFyoIr9CzVRm-HsFb46Z1ciTg,11746 +kivymd/uix/stacklayout.py,sha256=UDmj4W66Js16Y_RaHRA3WzXz1lBVwvhhtZkIIhQYfco,2067 +kivymd/uix/swiper/__init__.py,sha256=yjVyswA0HfoLRx9UW7k_GR497shvH5EthBbQk8CoU_4,43 +kivymd/uix/swiper/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/swiper/__pycache__/swiper.cpython-311.pyc,, +kivymd/uix/swiper/swiper.kv,sha256=iTaioAm-1WWmrGV4mqTizz3f4Qobv8WJZT-cwhEqyD0,291 +kivymd/uix/swiper/swiper.py,sha256=lG0_lK1TIfIIz5jkrSnQS67ecA9_Ty6DbwcTXN9olck,12618 +kivymd/uix/tab/__init__.py,sha256=Cr-a9CGP8XGLa7QkvU08OpIS1mXu2-o_8V2CONFH1fE,192 +kivymd/uix/tab/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/tab/__pycache__/tab.cpython-311.pyc,, +kivymd/uix/tab/tab.kv,sha256=ZBRG7PYPNPuI8pwfw7RNHI5wYgDP_oNPnZLzR4B_pMc,2255 +kivymd/uix/tab/tab.py,sha256=GTbl_ONS94t4-Lyb3tg5UXG6plx_t3QjDDZCxZtYyRQ,41727 +kivymd/uix/textfield/__init__.py,sha256=q0cpSrgMsws4Csqjz1xv-OCiiFkSstO9P1Vxi6H3sTo,195 +kivymd/uix/textfield/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/textfield/__pycache__/textfield.cpython-311.pyc,, +kivymd/uix/textfield/textfield.kv,sha256=Yu3KxUoBhim0ZI-7if-dY4HI0ZqhPA7mUHyLW7M4obo,13507 +kivymd/uix/textfield/textfield.py,sha256=G0freQCtBynqyvvfYrkqFg3tqfFyAOHla119eFworC0,60112 +kivymd/uix/tooltip/__init__.py,sha256=QPH-5XUDaiDQm6TO5gKyE7lUveO7F4Y4ZiWOfZkxD08,182 +kivymd/uix/tooltip/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/tooltip/__pycache__/tooltip.cpython-311.pyc,, +kivymd/uix/tooltip/tooltip.kv,sha256=gdM-7qQFuTcS-OXzsd6MgFZEalzgx_4wq5azAy1MxoM,2012 +kivymd/uix/tooltip/tooltip.py,sha256=I7UbwBjgaXQQJAsbYsSRZA3kskhZaOilUxav4akYfAM,14636 +kivymd/uix/transition/__init__.py,sha256=Akw-XkRM_JWV17J_SkneutkxlsKZZKqAEkCA4Rd8XYQ,141 +kivymd/uix/transition/__pycache__/__init__.cpython-311.pyc,, +kivymd/uix/transition/__pycache__/transition.cpython-311.pyc,, +kivymd/uix/transition/transition.py,sha256=6uWH24w6ZUK4abdQrc1ZuQqbOem9j_coS0zH16KvR-Q,17974 +kivymd/uix/widget.py,sha256=tR6SyGcoofexLHh_P9YUI7qcYrbHEzOkPwWQhpMoRJ4,1457 +kivymd/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +kivymd/utils/__pycache__/__init__.cpython-311.pyc,, +kivymd/utils/__pycache__/fpsmonitor.cpython-311.pyc,, +kivymd/utils/__pycache__/set_bars_colors.cpython-311.pyc,, +kivymd/utils/fpsmonitor.py,sha256=F1g2HqSr1QH2_SIoQReesMSMmyRj5YJxolBGlK1Tgs8,1514 +kivymd/utils/set_bars_colors.py,sha256=uhCQdrRhRW-P--5D5MJR6rcauOA8wmiFoapJJczkf14,4050 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/REQUESTED b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/REQUESTED similarity index 100% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/REQUESTED rename to kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/REQUESTED diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/WHEEL b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/WHEEL similarity index 65% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/WHEEL rename to kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/WHEEL index 0fde4dd..3d32758 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/WHEEL +++ b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: setuptools (74.1.2) +Generator: setuptools (74.1.3) Root-Is-Purelib: true Tag: py3-none-any diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/direct_url.json b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/direct_url.json new file mode 100644 index 0000000..c614191 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/direct_url.json @@ -0,0 +1 @@ +{"archive_info": {}, "url": "https://github.com/kivymd/KivyMD/archive/master.zip"} \ No newline at end of file diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/entry_points.txt b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/entry_points.txt similarity index 100% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/entry_points.txt rename to kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/entry_points.txt diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/top_level.txt b/kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/top_level.txt similarity index 100% rename from kivy_venv/lib/python3.11/site-packages/kivymd-1.2.0.dist-info/top_level.txt rename to kivy_venv/lib/python3.11/site-packages/kivymd-2.0.1.dev0.dist-info/top_level.txt diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/__init__.py index 58b78df..40f670c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/__init__.py @@ -26,12 +26,10 @@ import os import kivy from kivy.logger import Logger -__version__ = "1.2.0" -"""KivyMD version.""" +from kivymd._version import __version__, release -release = False if "READTHEDOCS" not in os.environ: - kivy.require("2.2.0") + kivy.require("2.3.0") try: from kivymd._version import __date__, __hash__, __short_hash__ @@ -59,13 +57,8 @@ _log_message = ( + f' (installed at "{__file__}")' ) Logger.info(_log_message) -Logger.warning( - "KivyMD: " - "Version 1.2.0 is deprecated and is no longer supported. " - "Use KivyMD version 2.0.0 from the master branch " - "(pip install https://github.com/kivymd/KivyMD/archive/master.zip)" -) import kivymd.factory_registers # NOQA import kivymd.font_definitions # NOQA +import kivymd.animation # NOQA from kivymd.tools.packaging.pyinstaller import hooks_path # NOQA diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/__init__.cpython-311.pyc index bbf0286..9d416f8 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/_version.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/_version.cpython-311.pyc index 36daae7..0bfc5a5 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/_version.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/_version.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/animation.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/animation.cpython-311.pyc new file mode 100644 index 0000000..e46ab29 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/animation.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/app.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/app.cpython-311.pyc index 3a9dfff..3fa8738 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/app.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/app.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/color_definitions.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/color_definitions.cpython-311.pyc deleted file mode 100644 index 9f472ad..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/color_definitions.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/dynamic_color.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/dynamic_color.cpython-311.pyc new file mode 100644 index 0000000..9e01241 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/dynamic_color.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/factory_registers.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/factory_registers.cpython-311.pyc index d622eb4..2b39ebc 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/factory_registers.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/factory_registers.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/font_definitions.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/font_definitions.cpython-311.pyc index 4e8f142..fdf5560 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/font_definitions.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/font_definitions.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/icon_definitions.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/icon_definitions.cpython-311.pyc index d3fa221..b3f6ece 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/icon_definitions.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/icon_definitions.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/material_resources.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/material_resources.cpython-311.pyc index 58d8ecd..d5954b3 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/material_resources.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/material_resources.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming.cpython-311.pyc index 289067c..ec4c9da 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming_dynamic_text.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming_dynamic_text.cpython-311.pyc deleted file mode 100644 index 451e86a..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/__pycache__/theming_dynamic_text.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/_version.py b/kivy_venv/lib/python3.11/site-packages/kivymd/_version.py index 5ab8670..c075297 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/_version.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/_version.py @@ -1,5 +1,5 @@ -# THIS FILE IS GENERATED FROM KIVYMD SETUP.PY -__version__ = '1.2.0' -__hash__ = 'Unknown' -__short_hash__ = 'Unknown' -__date__ = '2024-09-15' +release = False +__version__ = "2.0.1.dev0" +__hash__ = "Unknown" +__short_hash__ = "Unknown" +__date__ = "2024-09-15" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/animation.py b/kivy_venv/lib/python3.11/site-packages/kivymd/animation.py new file mode 100644 index 0000000..4987b57 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/animation.py @@ -0,0 +1,224 @@ +""" +Animation +========= + +.. versionadded:: 2.0.0 + +Adds new transitions to the :class:`~kivy.animation.AnimationTransition` class: +- "easing_standard" +- "easing_decelerated" +- "easing_accelerated" +- "easing_linear" + + +.. code-block:: python + + + from kivy.lang import Builder + from kivy.animation import Animation + from kivy.uix.boxlayout import BoxLayout + from kivy.clock import Clock + from kivy.metrics import dp + from kivy.properties import ListProperty + + from kivymd.app import MDApp + + + class AnimBox(BoxLayout): + obj_pos = ListProperty([0, 0]) + + + UI = ''' + : + transition:"in_out_bounce" + size_hint_y:None + height:dp(100) + obj_pos:[dp(40), self.pos[-1] + dp(40)] + canvas: + Color: + rgba:app.theme_cls.primaryContainerColor + Rectangle: + size:[self.size[0], dp(5)] + pos:self.pos[0], self.pos[-1] + dp(50) + Color: + rgba:app.theme_cls.primaryColor + Rectangle: + size:[dp(30)] * 2 + pos:root.obj_pos + MDLabel: + adaptive_height:True + text:root.transition + padding:[dp(10), 0] + halign:"center" + + MDGridLayout: + orientation:"lr-tb" + cols:1 + md_bg_color:app.theme_cls.backgroundColor + spacing:dp(10) + ''' + + + class MotionApp(MDApp): + + def build(self): + return Builder.load_string(UI) + + def on_start(self): + for transition in [ + "easing_linear", + "easing_accelerated", + "easing_decelerated", + "easing_standard", + "in_out_cubic" + ]: # Add more here for comparison + print(transition) + widget = AnimBox() + widget.transition = transition + self.root.add_widget(widget) + Clock.schedule_once(self.run_animation, 1) + + _inverse = True + def run_animation(self, dt): + x = (self.root.children[0].width - dp(30)) if self._inverse else 0 + for widget in self.root.children: + Animation( + obj_pos=[x, widget.obj_pos[-1]], t=widget.transition, d=3 + ).start(widget) + self._inverse = not self._inverse + Clock.schedule_once(self.run_animation, 3.1) + + MotionApp().run() + +.. image:: https://github.com/kivymd/KivyMD/assets/68729523/21c847b0-284a-4796-b704-e4a2531fbb1b + :align: center +""" + +import math +import sys +import kivy.animation + +float_epsilon = 8.3446500e-7 + +if sys.version_info < (3, 11): + cbrt = lambda number: (abs(number) ** (1/3)) * (-1 if number < 0 else 1) +else: + cbrt = math.cbrt + +class CubicBezier: + """Ported from Android source code""" + + p0 = 0 + p1 = 0 + p2 = 0 + p3 = 0 + + def __init__(self, *args): + self.p0, self.p1, self.p2, self.p3 = args + + def evaluate_cubic(self, p1, p2, t): + a = 1.0 / 3.0 + (p1 - p2) + b = p2 - 2.0 * p1 + c = p1 + return 3.0 * ((a * t + b) * t + c) * t + + def clamp_range(self, r): + if r < 0.0: + if -float_epsilon <= r < 0.0: + return 0.0 + else: + return math.nan + elif r > 1.0: + if 1.0 <= r <= 1.0 + float_epsilon: + return 1.0 + else: + return math.nan + else: + return r + + def close_to(self, x, y): + return abs(x - y) < float_epsilon + + def find_first_cubic_root(self, p0, p1, p2, p3): + a = 3.0 * (p0 - 2.0 * p1 + p2) + b = 3.0 * (p1 - p0) + c = p0 + d = -p0 + 3.0 * (p1 - p2) + p3 + if self.close_to(d, 0.0): + if self.close_to(a, 0.0): + if self.close_to(b, 0.0): + return math.nan + return self.clamp_range(-c / b) + else: + q = math.sqrt(b * b - 4.0 * a * c) + a2 = 2.0 * a + root = self.clamp_range((q - b) / a2) + if not math.isnan(root): + return root + return self.clamp_range((-b - q) / a2) + a /= d + b /= d + c /= d + o3 = (3.0 * b - a * a) / 9.0 + q2 = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0 + discriminant = q2 * q2 + o3 * o3 * o3 + a3 = a / 3.0 + + if discriminant < 0.0: + mp33 = -(o3 * o3 * o3) + r = math.sqrt(mp33) + t = -q2 / r + cos_phi = max(-1.0, min(t, 1.0)) + phi = math.acos(cos_phi) + t1 = 2.0 * cbrt(r) + root = self.clamp_range(t1 * math.cos(phi / 3.0) - a3) + if not math.isnan(root): + return root + root = self.clamp_range( + t1 * math.cos((phi + 2.0 * math.pi) / 3.0) - a3 + ) + if not math.isnan(root): + return root + return self.clamp_range( + t1 * math.cos((phi + 4.0 * math.pi) / 3.0) - a3 + ) + + elif self.close_to(discriminant, 0.0): + u1 = -cbrt(q2) + root = self.clamp_range(2.0 * u1 - a3) + if not math.isnan(root): + return root + return self.clamp_range(-u1 - a3) + + sd = math.sqrt(discriminant) + u1 = cbrt(-q2 + sd) + v1 = cbrt(q2 + sd) + return self.clamp_range(u1 - v1 - a3) + + def t(self, value: float): + return self.evaluate_cubic( + self.p1, + self.p3, + self.find_first_cubic_root( + -value, + self.p0 - value, + self.p2 - value, + 1.0 - value, + ), + ) + + +class MDAnimationTransition(kivy.animation.AnimationTransition): + """KivyMD's equivalent of kivy's `AnimationTransition`""" + + easing_standard = CubicBezier(0.4, 0.0, 0.2, 1.0).t + easing_decelerated = CubicBezier(0.0, 0.0, 0.2, 1.0).t + easing_accelerated = CubicBezier(0.4, 0.0, 1.0, 1.0).t + easing_linear = CubicBezier(0.0, 0.0, 1.0, 1.0).t + +# TODO: add `easing_emphasized` here +# it's defination is +# path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1) + +# Monkey patch kivy's animation module +kivy.animation.AnimationTransition = MDAnimationTransition diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/app.py b/kivy_venv/lib/python3.11/site-packages/kivymd/app.py index bdad688..40a31a1 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/app.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/app.py @@ -3,14 +3,15 @@ Themes/Material App =================== This module contains :class:`MDApp` class that is inherited from -:class:`~kivy.app.App`. :class:`MDApp` has some properties needed for ``KivyMD`` -library (like :attr:`~MDApp.theme_cls`). You can turn on the monitor displaying -the current ``FPS`` value in your application: +:class:`~kivy.app.App`. :class:`MDApp` has some properties needed for `KivyMD` +library (like :attr:`~MDApp.theme_cls`). You can turn on the monitor +displaying the current `FP` value in your application: .. code-block:: python KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor MDLabel: text: "Hello, World!" @@ -32,10 +33,23 @@ the current ``FPS`` value in your application: MainApp().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fps-monitor-dark.png :width: 350 px :align: center +.. note:: + + Note that if you override the built-in on_start method, you will + definitely need to call the super method: + + .. code-block:: python + + class MainApp(MDApp): + def build(self): + [...] + + def on_start(self): + [...] """ __all__ = ("MDApp",) @@ -55,7 +69,12 @@ class FpsMonitoring: """Implements a monitor to display the current FPS in the toolbar.""" def fps_monitor_start(self, anchor: str = "top") -> None: - """Adds a monitor to the main application window.""" + """ + Adds a monitor to the main application window. + + :type anchor: str; + :param anchor: anchor FPS panel ('top' or 'bottom'); + """ def add_monitor(*args): from kivy.core.window import Window @@ -114,6 +133,14 @@ class MDApp(App, FpsMonitoring): super().__init__(**kwargs) self.theme_cls = ThemeManager() + def _run_prepare(self): + self.theme_cls.bind( + theme_style=self.theme_cls.update_theme_colors, + primary_palette=self.theme_cls.set_colors + ) + self.theme_cls.set_colors() + super()._run_prepare() + def load_all_kv_files(self, path_to_directory: str) -> None: """ Recursively loads KV files from the selected directory. @@ -128,8 +155,8 @@ class MDApp(App, FpsMonitoring): if "kivymd" in path_to_directory: Logger.critical( "KivyMD: " - "Do not use the word 'kivymd' in the name of the directory " - "from where you download KV files" + "Do not use the word 'kivymd' in the name of the " + "directory from where you download KV files" ) if ( "venv" in path_to_dir diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/color_definitions.py b/kivy_venv/lib/python3.11/site-packages/kivymd/color_definitions.py deleted file mode 100644 index e4a7de4..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/color_definitions.py +++ /dev/null @@ -1,954 +0,0 @@ -""" -Themes/Color Definitions -======================== - -.. seealso:: - - `Material Design spec, The color system `_ - - `Material Design spec, The color tool `_ - -Material colors palette to use in :class:`kivymd.theming.ThemeManager`. -:data:`~colors` is a dict-in-dict where the first key is a value from -:data:`~palette` and the second key is a value from :data:`~hue`. Color is a hex -value, a string of 6 characters (0-9, A-F) written in uppercase. - -For example, ``colors["Red"]["900"]`` is ``"B71C1C"``. -""" - -colors = { - "Red": { - "50": "FFEBEE", - "100": "FFCDD2", - "200": "EF9A9A", - "300": "E57373", - "400": "EF5350", - "500": "F44336", - "600": "E53935", - "700": "D32F2F", - "800": "C62828", - "900": "B71C1C", - "A100": "FF8A80", - "A200": "FF5252", - "A400": "FF1744", - "A700": "D50000", - }, - "Pink": { - "50": "FCE4EC", - "100": "F8BBD0", - "200": "F48FB1", - "300": "F06292", - "400": "EC407A", - "500": "E91E63", - "600": "D81B60", - "700": "C2185B", - "800": "AD1457", - "900": "880E4F", - "A100": "FF80AB", - "A200": "FF4081", - "A400": "F50057", - "A700": "C51162", - }, - "Purple": { - "50": "F3E5F5", - "100": "E1BEE7", - "200": "CE93D8", - "300": "BA68C8", - "400": "AB47BC", - "500": "9C27B0", - "600": "8E24AA", - "700": "7B1FA2", - "800": "6A1B9A", - "900": "4A148C", - "A100": "EA80FC", - "A200": "E040FB", - "A400": "D500F9", - "A700": "AA00FF", - }, - "DeepPurple": { - "50": "EDE7F6", - "100": "D1C4E9", - "200": "B39DDB", - "300": "9575CD", - "400": "7E57C2", - "500": "673AB7", - "600": "5E35B1", - "700": "512DA8", - "800": "4527A0", - "900": "311B92", - "A100": "B388FF", - "A200": "7C4DFF", - "A400": "651FFF", - "A700": "6200EA", - }, - "Indigo": { - "50": "E8EAF6", - "100": "C5CAE9", - "200": "9FA8DA", - "300": "7986CB", - "400": "5C6BC0", - "500": "3F51B5", - "600": "3949AB", - "700": "303F9F", - "800": "283593", - "900": "1A237E", - "A100": "8C9EFF", - "A200": "536DFE", - "A400": "3D5AFE", - "A700": "304FFE", - }, - "Blue": { - "50": "E3F2FD", - "100": "BBDEFB", - "200": "90CAF9", - "300": "64B5F6", - "400": "42A5F5", - "500": "2196F3", - "600": "1E88E5", - "700": "1976D2", - "800": "1565C0", - "900": "0D47A1", - "A100": "82B1FF", - "A200": "448AFF", - "A400": "2979FF", - "A700": "2962FF", - }, - "LightBlue": { - "50": "E1F5FE", - "100": "B3E5FC", - "200": "81D4FA", - "300": "4FC3F7", - "400": "29B6F6", - "500": "03A9F4", - "600": "039BE5", - "700": "0288D1", - "800": "0277BD", - "900": "01579B", - "A100": "80D8FF", - "A200": "40C4FF", - "A400": "00B0FF", - "A700": "0091EA", - }, - "Cyan": { - "50": "E0F7FA", - "100": "B2EBF2", - "200": "80DEEA", - "300": "4DD0E1", - "400": "26C6DA", - "500": "00BCD4", - "600": "00ACC1", - "700": "0097A7", - "800": "00838F", - "900": "006064", - "A100": "84FFFF", - "A200": "18FFFF", - "A400": "00E5FF", - "A700": "00B8D4", - }, - "Teal": { - "50": "E0F2F1", - "100": "B2DFDB", - "200": "80CBC4", - "300": "4DB6AC", - "400": "26A69A", - "500": "009688", - "600": "00897B", - "700": "00796B", - "800": "00695C", - "900": "004D40", - "A100": "A7FFEB", - "A200": "64FFDA", - "A400": "1DE9B6", - "A700": "00BFA5", - }, - "Green": { - "50": "E8F5E9", - "100": "C8E6C9", - "200": "A5D6A7", - "300": "81C784", - "400": "66BB6A", - "500": "4CAF50", - "600": "43A047", - "700": "388E3C", - "800": "2E7D32", - "900": "1B5E20", - "A100": "B9F6CA", - "A200": "69F0AE", - "A400": "00E676", - "A700": "00C853", - }, - "LightGreen": { - "50": "F1F8E9", - "100": "DCEDC8", - "200": "C5E1A5", - "300": "AED581", - "400": "9CCC65", - "500": "8BC34A", - "600": "7CB342", - "700": "689F38", - "800": "558B2F", - "900": "33691E", - "A100": "CCFF90", - "A200": "B2FF59", - "A400": "76FF03", - "A700": "64DD17", - }, - "Lime": { - "50": "F9FBE7", - "100": "F0F4C3", - "200": "E6EE9C", - "300": "DCE775", - "400": "D4E157", - "500": "CDDC39", - "600": "C0CA33", - "700": "AFB42B", - "800": "9E9D24", - "900": "827717", - "A100": "F4FF81", - "A200": "EEFF41", - "A400": "C6FF00", - "A700": "AEEA00", - }, - "Yellow": { - "50": "FFFDE7", - "100": "FFF9C4", - "200": "FFF59D", - "300": "FFF176", - "400": "FFEE58", - "500": "FFEB3B", - "600": "FDD835", - "700": "FBC02D", - "800": "F9A825", - "900": "F57F17", - "A100": "FFFF8D", - "A200": "FFFF00", - "A400": "FFEA00", - "A700": "FFD600", - }, - "Amber": { - "50": "FFF8E1", - "100": "FFECB3", - "200": "FFE082", - "300": "FFD54F", - "400": "FFCA28", - "500": "FFC107", - "600": "FFB300", - "700": "FFA000", - "800": "FF8F00", - "900": "FF6F00", - "A100": "FFE57F", - "A200": "FFD740", - "A400": "FFC400", - "A700": "FFAB00", - }, - "Orange": { - "50": "FFF3E0", - "100": "FFE0B2", - "200": "FFCC80", - "300": "FFB74D", - "400": "FFA726", - "500": "FF9800", - "600": "FB8C00", - "700": "F57C00", - "800": "EF6C00", - "900": "E65100", - "A100": "FFD180", - "A200": "FFAB40", - "A400": "FF9100", - "A700": "FF6D00", - }, - "DeepOrange": { - "50": "FBE9E7", - "100": "FFCCBC", - "200": "FFAB91", - "300": "FF8A65", - "400": "FF7043", - "500": "FF5722", - "600": "F4511E", - "700": "E64A19", - "800": "D84315", - "900": "BF360C", - "A100": "FF9E80", - "A200": "FF6E40", - "A400": "FF3D00", - "A700": "DD2C00", - }, - "Brown": { - "50": "EFEBE9", - "100": "D7CCC8", - "200": "BCAAA4", - "300": "A1887F", - "400": "8D6E63", - "500": "795548", - "600": "6D4C41", - "700": "5D4037", - "800": "4E342E", - "900": "3E2723", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Gray": { - "50": "FAFAFA", - "100": "F5F5F5", - "200": "EEEEEE", - "300": "E0E0E0", - "400": "BDBDBD", - "500": "9E9E9E", - "600": "757575", - "700": "616161", - "800": "424242", - "900": "212121", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "BlueGray": { - "50": "ECEFF1", - "100": "CFD8DC", - "200": "B0BEC5", - "300": "90A4AE", - "400": "78909C", - "500": "607D8B", - "600": "546E7A", - "700": "455A64", - "800": "37474F", - "900": "263238", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Light": { - "StatusBar": "E0E0E0", - "AppBar": "F5F5F5", - "Background": "FAFAFA", - "CardsDialogs": "FFFFFF", - "FlatButtonDown": "cccccc", - }, - "Dark": { - "StatusBar": "000000", - "AppBar": "1f1f1f", - "Background": "121212", - "CardsDialogs": "212121", - "FlatButtonDown": "999999", - }, -} -""" -Color palette. Taken from `2014 Material Design color palettes -`_. - -To demonstrate the shades of the palette, you can run the following code: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import ListProperty, StringProperty - - from kivymd.color_definitions import colors - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.boxlayout import MDBoxLayout - - demo = ''' - - orientation: 'vertical' - - MDTopAppBar: - title: app.title - - MDTabs: - id: android_tabs - on_tab_switch: app.on_tab_switch(*args) - size_hint_y: None - height: "48dp" - tab_indicator_anim: False - - RecycleView: - id: rv - key_viewclass: "viewclass" - key_size: "height" - - RecycleBoxLayout: - default_size: None, dp(48) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - - - - size_hint_y: None - height: "42dp" - - MDLabel: - text: root.text - halign: "center" - - - - ''' - - from kivy.factory import Factory - - from kivymd.app import MDApp - - - class Tab(MDBoxLayout, MDTabsBase): - pass - - - class ItemColor(MDBoxLayout): - text = StringProperty() - color = ListProperty() - - - class Palette(MDApp): - title = "Colors definitions" - - def build(self): - Builder.load_string(demo) - self.screen = Factory.Root() - - for name_tab in colors.keys(): - tab = Tab(title=name_tab) - self.screen.ids.android_tabs.add_widget(tab) - return self.screen - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tabs_label, tab_text - ): - self.screen.ids.rv.data = [] - if not tab_text: - tab_text = 'Red' - for value_color in colors[tab_text]: - self.screen.ids.rv.data.append( - { - "viewclass": "ItemColor", - "md_bg_color": colors[tab_text][value_color], - "title": value_color, - } - ) - - def on_start(self): - self.on_tab_switch( - None, - None, - None, - self.screen.ids.android_tabs.ids.layout.children[-1].text, - ) - - - Palette().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/palette.gif - :align: center -""" - -palette = [ - "Red", - "Pink", - "Purple", - "DeepPurple", - "Indigo", - "Blue", - "LightBlue", - "Cyan", - "Teal", - "Green", - "LightGreen", - "Lime", - "Yellow", - "Amber", - "Orange", - "DeepOrange", - "Brown", - "Gray", - "BlueGray", -] -"""Valid values for color palette selecting.""" - -hue = [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "900", - "A100", - "A200", - "A400", - "A700", -] -"""Valid values for color hue selecting.""" - - -light_colors = { - "Red": ["50", "100", "200", "300", "A100"], - "Pink": ["50", "100", "200", "A100"], - "Purple": ["50", "100", "200", "A100"], - "DeepPurple": ["50", "100", "200", "A100"], - "Indigo": ["50", "100", "200", "A100"], - "Blue": ["50", "100", "200", "300", "400", "A100"], - "LightBlue": [ - "50", - "100", - "200", - "300", - "400", - "500", - "A100", - "A200", - "A400", - ], - "Cyan": [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "A100", - "A200", - "A400", - "A700", - ], - "Teal": ["50", "100", "200", "300", "400", "A100", "A200", "A400", "A700"], - "Green": [ - "50", - "100", - "200", - "300", - "400", - "500", - "A100", - "A200", - "A400", - "A700", - ], - "LightGreen": [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "A100", - "A200", - "A400", - "A700", - ], - "Lime": [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "A100", - "A200", - "A400", - "A700", - ], - "Yellow": [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "900", - "A100", - "A200", - "A400", - "A700", - ], - "Amber": [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "800", - "900", - "A100", - "A200", - "A400", - "A700", - ], - "Orange": [ - "50", - "100", - "200", - "300", - "400", - "500", - "600", - "700", - "A100", - "A200", - "A400", - "A700", - ], - "DeepOrange": ["50", "100", "200", "300", "400", "A100", "A200"], - "Brown": ["50", "100", "200"], - "Gray": ["50", "100", "200", "300", "400", "500"], - "BlueGray": ["50", "100", "200", "300"], - "Dark": [], - "Light": ["White", "MainBackground", "DialogBackground"], -} -"""Which colors are light. Other are dark.""" - -text_colors = { - "Red": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "Pink": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "FFFFFF", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "Purple": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "FFFFFF", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "DeepPurple": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "FFFFFF", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "Indigo": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "FFFFFF", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "Blue": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "LightBlue": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "FFFFFF", - }, - "Cyan": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "000000", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Teal": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Green": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "LightGreen": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "000000", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Lime": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "000000", - "700": "000000", - "800": "000000", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Yellow": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "000000", - "700": "000000", - "800": "000000", - "900": "000000", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Amber": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "000000", - "700": "000000", - "800": "000000", - "900": "000000", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "Orange": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "000000", - "700": "000000", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "000000", - "A700": "000000", - }, - "DeepOrange": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "000000", - "A200": "000000", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "Brown": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "FFFFFF", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "FFFFFF", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "Gray": { - "50": "FFFFFF", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "000000", - "500": "000000", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "FFFFFF", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, - "BlueGray": { - "50": "000000", - "100": "000000", - "200": "000000", - "300": "000000", - "400": "FFFFFF", - "500": "FFFFFF", - "600": "FFFFFF", - "700": "FFFFFF", - "800": "FFFFFF", - "900": "FFFFFF", - "A100": "FFFFFF", - "A200": "FFFFFF", - "A400": "FFFFFF", - "A700": "FFFFFF", - }, -} -""" -Text colors generated from :data:`~light_colors`. "000000" for light and -"FFFFFF" for dark. - -How to generate text_colors dict - -.. code-block:: python - - text_colors = {} - for p in palette: - text_colors[p] = {} - for h in hue: - if h in light_colors[p]: - text_colors[p][h] = "000000" - else: - text_colors[p][h] = "FFFFFF" -""" - -theme_colors = [ - "Primary", - "Secondary", - "Background", - "Surface", - "Error", - "On_Primary", - "On_Secondary", - "On_Background", - "On_Surface", - "On_Error", -] -"""Valid theme colors.""" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/dynamic_color.py b/kivy_venv/lib/python3.11/site-packages/kivymd/dynamic_color.py new file mode 100644 index 0000000..bbe8b26 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/dynamic_color.py @@ -0,0 +1,632 @@ +""" +Components/Dynamic color +======================== + +.. seealso:: + + `Material Design spec, Dynamic color + `_ + +.. rubric:: Dynamic color can create accessible UI color schemes based on + content or user settings + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color.png + :align: center + +Dynamic color experiences are built with M3 color schemes. Beginning with +Android 12, users can generate individualized schemes through wallpaper +selection and other customization settings. With M3 as a foundation, +user-generated colors can coexist with app colors, putting a range of +customizable visual experiences in the hands of users. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color-preview.png + :align: center + +1. Baseline scheme +2. Colors extracted from a wallpaper +3. Colors extracted from content + +Example of dynamic color from the list of standard color schemes +---------------------------------------------------------------- + +.. code-block:: python + + from kivy.clock import Clock + from kivy.lang import Builder + from kivy.properties import StringProperty, ColorProperty + from kivy.uix.boxlayout import BoxLayout + from kivy.utils import hex_colormap + + from kivymd.uix.menu import MDDropdownMenu + from kivymd.app import MDApp + + + KV = ''' + + orientation: "vertical" + + MDLabel: + text: root.text + color: "grey" + adaptive_height: True + + MDCard: + theme_bg_color: "Custom" + md_bg_color: root.bg_color + + + MDScreen: + md_bg_color: app.theme_cls.backgroundColor + + MDIconButton: + on_release: app.open_menu(self) + pos_hint: {"top": .98} + x: "12dp" + icon: "menu" + + MDRecycleView: + id: card_list + viewclass: "ColorCard" + bar_width: 0 + size_hint_y: None + height: root.height - dp(68) + + RecycleGridLayout: + cols: 3 + spacing: "16dp" + padding: "16dp" + default_size: None, dp(56) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + ''' + + + class ColorCard(BoxLayout): + text = StringProperty() + bg_color = ColorProperty() + + + class Example(MDApp): + menu: MDDropdownMenu = None + + def build(self): + self.theme_cls.dynamic_color = True + return Builder.load_string(KV) + + def get_instance_from_menu(self, name_item): + index = 0 + rv = self.menu.ids.md_menu + opts = rv.layout_manager.view_opts + datas = rv.data[0] + + for data in rv.data: + if data["text"] == name_item: + index = rv.data.index(data) + break + + instance = rv.view_adapter.get_view( + index, datas, opts[index]["viewclass"] + ) + + return instance + + def open_menu(self, menu_button): + menu_items = [] + for item, method in { + "Set palette": lambda: self.set_palette(), + "Switch theme style": lambda: self.theme_switch(), + }.items(): + menu_items.append({"text": item, "on_release": method}) + self.menu = MDDropdownMenu( + caller=menu_button, + items=menu_items, + ) + self.menu.open() + + def set_palette(self): + instance_from_menu = self.get_instance_from_menu("Set palette") + available_palettes = [ + name_color.capitalize() for name_color in hex_colormap.keys() + ] + + menu_items = [] + for name_palette in available_palettes: + menu_items.append( + { + "text": name_palette, + "on_release": lambda x=name_palette: self.switch_palette(x), + } + ) + MDDropdownMenu( + caller=instance_from_menu, + items=menu_items, + ).open() + + def switch_palette(self, selected_palette): + self.theme_cls.primary_palette = selected_palette + Clock.schedule_once(self.generate_cards, 0.5) + + def theme_switch(self) -> None: + self.theme_cls.switch_theme() + Clock.schedule_once(self.generate_cards, 0.5) + + def generate_cards(self, *args): + self.root.ids.card_list.data = [] + for color in self.theme_cls.schemes_name_colors: + value = f"{color}Color" + self.root.ids.card_list.data.append( + { + "bg_color": getattr(self.theme_cls, value), + "text": value, + } + ) + + def on_start(self): + Clock.schedule_once(self.generate_cards) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color.gif + :align: center + +Example of a dynamic color from an image +---------------------------------------- + +.. seealso:: + + :attr:`kivymd.theming.ThemeManager.path_to_wallpaper` + +.. code-block:: python + + import os + + from kivy.clock import Clock + from kivy.core.window import Window + from kivy.core.window.window_sdl2 import WindowSDL + from kivy.lang import Builder + from kivy.properties import StringProperty, ColorProperty + + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.app import MDApp + + + KV = ''' + + orientation: "vertical" + + MDLabel: + text: root.text + color: "grey" + adaptive_height: True + + MDCard: + theme_bg_color: "Custom" + md_bg_color: root.bg_color + + + MDScreen: + md_bg_color: app.theme_cls.backgroundColor + + MDRecycleView: + id: card_list + viewclass: "ColorCard" + bar_width: 0 + + RecycleGridLayout: + cols: 3 + spacing: "16dp" + padding: "16dp" + default_size: None, dp(56) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + ''' + + + class ColorCard(MDBoxLayout): + text = StringProperty() + bg_color = ColorProperty() + + + class Example(MDApp): + def __init__(self, **kwargs): + super().__init__(**kwargs) + Window.bind(on_dropfile=self.on_drop_file) + + def on_drop_file(self, sdl: WindowSDL, path_to_file: str) -> None: + ext = os.path.splitext(path_to_file)[1] + if isinstance(path_to_file, bytes): + path_to_file = path_to_file.decode() + if isinstance(ext, bytes): + ext = ext.decode() + if ext in [".png", ".jpg"]: + self.theme_cls.path_to_wallpaper = path_to_file + Clock.schedule_once(self.generate_cards, 0.5) + + def build(self): + self.theme_cls.dynamic_color = True + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + def theme_switch(self) -> None: + self.theme_cls.switch_theme() + Clock.schedule_once(self.generate_cards, 0.5) + + def generate_cards(self, *args): + self.root.ids.card_list.data = [] + for color in self.theme_cls.schemes_name_colors: + value = f"{color}Color" + self.root.ids.card_list.data.append( + { + "bg_color": getattr(self.theme_cls, value), + "text": value, + } + ) + + def on_start(self): + Clock.schedule_once(self.generate_cards) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dynamic-color-path-to_wallpapper.gif + :align: center +""" + +from kivy.properties import ColorProperty + + +class DynamicColor: + """ + Dynamic color class. + + .. versionadded:: 2.0.0 + """ + + # Primary. + primaryColor = ColorProperty() + """ + Primary color. + + :attr:`primaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + primaryContainerColor = ColorProperty() + """ + Primary container color. + + :attr:`primaryContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # On Primary. + onPrimaryColor = ColorProperty() + """ + On primary color. + + :attr:`onPrimaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + onPrimaryContainerColor = ColorProperty() + """ + On primary container color. + + :attr:`onPrimaryContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Secondary. + secondaryColor = ColorProperty() + """ + Secondary color. + + :attr:`secondaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + secondaryContainerColor = ColorProperty() + """ + Secondary container color. + + :attr:`secondaryContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # On Secondary. + onSecondaryColor = ColorProperty() + """ + On secondary color. + + :attr:`onSecondaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + onSecondaryContainerColor = ColorProperty() + """ + On secondary container color. + + :attr:`onSecondaryContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Tertiary. + tertiaryColor = ColorProperty() + """ + Tertiary color. + + :attr:`tertiaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + tertiaryContainerColor = ColorProperty() + """ + Tertiary container color. + + :attr:`tertiaryContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # On Tertiary. + onTertiaryColor = ColorProperty() + """ + On tertiary color. + + :attr:`onTertiaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + onTertiaryContainerColor = ColorProperty() + """ + On tertiary container color. + + :attr:`onTertiaryContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Surface. + surfaceColor = ColorProperty() + """ + Surface color. + + :attr:`surfaceColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceDimColor = ColorProperty() + """ + Surface dim color. + + :attr:`surfaceDimColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceBrightColor = ColorProperty() + """ + Surface bright color. + + :attr:`surfaceBrightColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceContainerLowestColor = ColorProperty() + """ + Surface container lowest color. + + :attr:`surfaceContainerLowestColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceContainerLowColor = ColorProperty() + """ + Surface container low color. + + :attr:`surfaceContainerLowColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceContainerColor = ColorProperty() + """ + Surface container color. + + :attr:`surfaceContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceContainerHighColor = ColorProperty() + """ + Surface container high color. + + :attr:`surfaceContainerHighColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceContainerHighestColor = ColorProperty() + """ + Surface container highest color. + + :attr:`surfaceContainerHighestColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceVariantColor = ColorProperty() + """ + Surface variant color. + + :attr:`surfaceVariantColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + surfaceTintColor = ColorProperty() + """ + Surface tint color. + + :attr:`surfaceTintColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # On Surface. + onSurfaceColor = ColorProperty() + """ + On surface color. + + :attr:`onSurfaceColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + onSurfaceLightColor = ColorProperty() + """ + On surface light color. + + :attr:`onSurfaceLightColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + onSurfaceVariantColor = ColorProperty() + """ + On surface variant color. + + :attr:`onSurfaceVariantColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Inverse. + inverseSurfaceColor = ColorProperty() + """ + Inverse surface color. + + :attr:`inverseSurfaceColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + inverseOnSurfaceColor = ColorProperty() + """ + Inverse on surface color. + + :attr:`inverseOnSurfaceColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + inversePrimaryColor = ColorProperty() + """ + Inverse primary color. + + :attr:`inversePrimaryColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Background. + backgroundColor = ColorProperty() + """ + Background color. + + :attr:`backgroundColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # On Background. + onBackgroundColor = ColorProperty() + """ + On background color. + + :attr:`onBackgroundColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Error. + errorColor = ColorProperty() + """ + Error color. + + :attr:`errorColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + errorContainerColor = ColorProperty() + """ + Error container color. + + :attr:`errorContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # On Error. + onErrorColor = ColorProperty() + """ + On error color. + + :attr:`onErrorColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + onErrorContainerColor = ColorProperty() + """ + On error container color. + + :attr:`onErrorContainerColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Outline. + outlineColor = ColorProperty() + """ + Outline color. + + :attr:`outlineColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + outlineVariantColor = ColorProperty() + """ + Outline variant color. + + :attr:`outlineVariantColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Shadow/scrim. + shadowColor = ColorProperty() + """ + Shadow color. + + :attr:`shadowColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + scrimColor = ColorProperty() + """ + Scrim color. + + :attr:`scrimColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Disabled. + disabledTextColor = ColorProperty() + """ + Disabled text color. + + :attr:`disabledTextColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # Transparent. + transparentColor = ColorProperty([0, 0, 0, 0]) + """ + Transparent color. + + :attr:`transparentColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0]`. + """ + + # Ripple. + rippleColor = ColorProperty("#BDBDBD") + """ + Ripple color. + + :attr:`rippleColor` is an :class:`~kivy.properties.ColorProperty` + and defaults to `'#BDBDBD'`. + """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/__pycache__/__init__.cpython-311.pyc index c8885d5..008b0bd 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__init__.py deleted file mode 100644 index d2905b0..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .fadingedge import FadingEdgeEffect diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 06c12bd..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__pycache__/fadingedge.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__pycache__/fadingedge.cpython-311.pyc deleted file mode 100644 index d77e538..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/__pycache__/fadingedge.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/fadingedge.py b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/fadingedge.py deleted file mode 100644 index 106d3c8..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/fadingedge/fadingedge.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -Effects/FadingEdgeEffect -======================== - -.. versionadded:: 1.0.0 - -The `FadingEdgeEffect` class implements a fade effect for `KivyMD` widgets: - -.. code-block:: python - - from kivy.lang import Builder - from kivy.uix.scrollview import ScrollView - - from kivymd.app import MDApp - from kivymd.effects.fadingedge.fadingedge import FadingEdgeEffect - from kivymd.uix.list import OneLineListItem - - KV = ''' - MDScreen: - - FadeScrollView: - fade_height: self.height / 2 - fade_color: root.md_bg_color - - MDList: - id: container - ''' - - - class FadeScrollView(FadingEdgeEffect, ScrollView): - pass - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(20): - self.root.ids.container.add_widget( - OneLineListItem(text=f"Single-line item {i}") - ) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fading-edge-effect-white.gif - :align: center - -.. note:: Use the same color value for the fade_color parameter as for the - parent widget. -""" - -from typing import Union - -from kivy.clock import Clock -from kivy.graphics.context_instructions import Color -from kivy.graphics.vertex_instructions import Rectangle -from kivy.metrics import dp -from kivy.properties import BooleanProperty, ColorProperty, NumericProperty - -from kivymd.theming import ThemableBehavior - -__all_ = ("FadingEdgeEffect",) - - -class FadingEdgeEffect(ThemableBehavior): - """ - The class implements the fade effect. - - .. versionadded:: 1.0.0 - """ - - fade_color = ColorProperty(None) - """ - Fade color. - - :attr:`fade_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - fade_height = NumericProperty(0) - """ - Fade height. - - :attr:`fade_height` is an :class:`~kivy.properties.ColorProperty` - and defaults to `0`. - """ - - edge_top = BooleanProperty(True) - """ - Display fade edge top. - - :attr:`edge_top` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - edge_bottom = BooleanProperty(True) - """ - Display fade edge bottom. - - :attr:`edge_bottom` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - _height_segment = 10 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_fade) - - # TODO: Perhaps it would be better if we used a Shader for the fade effect. - # But, I think the canvas instructions shouldn't affect performance - def set_fade(self, interval: Union[int, float]) -> None: - """Draws a bottom and top fade border on the canvas.""" - - fade_color = ( - self.theme_cls.primary_color - if not self.fade_color - else self.fade_color - ) - height_segment = ( - self.fade_height if self.fade_height else dp(100) - ) // self._height_segment - alpha = 1.1 - - with self.canvas: - for i in range(self._height_segment): - alpha -= 0.1 - - Color(rgba=(fade_color[:-1] + [round(alpha, 1)])) - rectangle_top = ( - Rectangle( - pos=(self.x, self.height - (i * height_segment)), - size=(self.width, height_segment), - ) - if self.edge_top - else None - ) - rectangle_bottom = ( - Rectangle( - pos=(self.x, i * height_segment), - size=(self.width, height_segment), - ) - if self.edge_bottom - else None - ) - # How I hate lambda functions because of their length :( - # But I don’t want to call the arguments by short, - # incomprehensible names 'a', 'b', 'c'. - self.bind( - pos=lambda instance_fadind_edge_effect, window_size, rectangle_top=rectangle_top, rectangle_bottom=rectangle_bottom, index=i: self.update_canvas( - instance_fadind_edge_effect, - window_size, - rectangle_top, - rectangle_bottom, - index, - ), - size=lambda instance_fadind_edge_effect, window_size, rectangle_top=rectangle_top, rectangle_bottom=rectangle_bottom, index=i: self.update_canvas( - instance_fadind_edge_effect, - window_size, - rectangle_top, - rectangle_bottom, - index, - ), - ) - self.update_canvas( - self, self.size, rectangle_top, rectangle_bottom, i - ) - - def update_canvas( - self, - instance_fadind_edge_effect, - size: list[int, int], - rectangle_top: Rectangle, - rectangle_bottom: Rectangle, - index: int, - ) -> None: - """ - Updates the position and size of the fade border on the canvas. - Called when the application screen is resized. - """ - - height_segment = ( - self.fade_height if self.fade_height else dp(100) - ) // self._height_segment - - if rectangle_top: - rectangle_top.pos = ( - instance_fadind_edge_effect.x, - size[1] - - (index * height_segment - instance_fadind_edge_effect.y), - ) - rectangle_top.size = (size[0], height_segment) - if rectangle_bottom: - rectangle_bottom.pos = ( - instance_fadind_edge_effect.x, - index * height_segment + instance_fadind_edge_effect.y, - ) - rectangle_bottom.size = (size[0], height_segment) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__init__.py deleted file mode 100644 index 2fbddae..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .roulettescroll import RouletteScrollEffect diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index a4379aa..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__pycache__/roulettescroll.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__pycache__/roulettescroll.cpython-311.pyc deleted file mode 100644 index 360857b..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/__pycache__/roulettescroll.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/roulettescroll.py b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/roulettescroll.py deleted file mode 100644 index 528f5ac..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/roulettescroll/roulettescroll.py +++ /dev/null @@ -1,251 +0,0 @@ -""" -Effects/RouletteScrollEffect -============================ - -This is a subclass of :class:`kivy.effects.ScrollEffect` that simulates the -motion of a roulette, or a notched wheel (think Wheel of Fortune). It is -primarily designed for emulating the effect of the iOS and android date pickers. - -Usage ------ - -Here's an example of using :class:`RouletteScrollEffect` for a -:class:`kivy.uix.scrollview.ScrollView`: - -.. code-block:: python - - from kivy.uix.gridlayout import GridLayout - from kivy.uix.button import Button - from kivy.uix.scrollview import ScrollView - - # Preparing a `GridLayout` inside a `ScrollView`. - layout = GridLayout(cols=1, padding=10, size_hint=(None, None), width=500) - layout.bind(minimum_height=layout.setter('height')) - - for i in range(30): - btn = Button(text=str(i), size=(480, 40), size_hint=(None, None)) - layout.add_widget(btn) - - root = ScrollView( - size_hint=(None, None), - size=(500, 320), - pos_hint={'center_x': .5, 'center_y': .5}, - do_scroll_x=False, - ) - root.add_widget(layout) - - # Preparation complete. Now add the new scroll effect. - root.effect_y = RouletteScrollEffect(anchor=20, interval=40) - runTouchApp(root) - -Here the :class:`ScrollView` scrolls through a series of buttons with height -40. We then attached a :class:`RouletteScrollEffect` with interval 40, -corresponding to the button heights. This allows the scrolling to stop at -the same offset no matter where it stops. The :attr:`RouletteScrollEffect.anchor` -adjusts this offset. - -Customizations --------------- - -Other settings that can be played with include: - -:attr:`RouletteScrollEffect.pull_duration`, -:attr:`RouletteScrollEffect.coasting_alpha`, -:attr:`RouletteScrollEffect.pull_back_velocity`, and -:attr:`RouletteScrollEffect.terminal_velocity`. - -See their module documentations for details. - -:class:`RouletteScrollEffect` has one event ``on_coasted_to_stop`` that -is fired when the roulette stops, "making a selection". It can be listened to -for handling or cleaning up choice making. -""" - -from math import ceil, exp, floor - -from kivy.animation import Animation -from kivy.effects.scroll import ScrollEffect -from kivy.properties import AliasProperty, NumericProperty, ObjectProperty - -__all_ = ("RouletteScrollEffect",) - - -class RouletteScrollEffect(ScrollEffect): - """ - This is a subclass of :class:`kivy.effects.ScrollEffect` that simulates the - motion of a roulette, or a notched wheel (think Wheel of Fortune). It is - primarily designed for emulating the effect of the iOS and android date pickers. - - .. versionadded:: 0.104.2 - """ - - __events__ = ("on_coasted_to_stop",) - - drag_threshold = NumericProperty(0) - """ - Overrides :attr:`ScrollEffect.drag_threshold` to abolish drag threshold. - - .. note:: - If using this with a :class:`Roulette` or other :class:`Tickline` - subclasses, what matters is :attr:`Tickline.drag_threshold`, which - is passed to this attribute in the end. - - :attr:`drag_threshold` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - min = NumericProperty(-float("inf")) - max = NumericProperty(float("inf")) - - interval = NumericProperty(50) - """ - The interval of the values of the "roulette". - - :attr:`interval` is an :class:`~kivy.properties.NumericProperty` - and defaults to `50`. - """ - - anchor = NumericProperty(0) - """ - One of the valid stopping values. - - :attr:`anchor` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - pull_duration = NumericProperty(0.2) - """ - When movement slows around a stopping value, an animation is used - to pull it toward the nearest value. :attr:`pull_duration` is the duration - used for such an animation. - - :attr:`pull_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - coasting_alpha = NumericProperty(0.5) - """ - When within :attr:`coasting_alpha` * :attr:`interval` of the - next notch and velocity is below :attr:`terminal_velocity`, - coasting begins and will end on the next notch. - - :attr:`coasting_alpha` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.5`. - """ - - pull_back_velocity = NumericProperty("50sp") - """ - The velocity below which the scroll value will be drawn to the - *nearest* notch instead of the *next* notch in the direction travelled. - - :attr:`pull_back_velocity` is an :class:`~kivy.properties.NumericProperty` - and defaults to `50sp`. - """ - - _anim = ObjectProperty(None) - - def get_term_vel(self): - return ( - exp(self.friction) - * self.interval - * self.coasting_alpha - / self.pull_duration - ) - - def set_term_vel(self, val): - self.pull_duration = ( - exp(self.friction) * self.interval * self.coasting_alpha / val - ) - - terminal_velocity = AliasProperty( - get_term_vel, - set_term_vel, - bind=["interval", "coasting_alpha", "pull_duration", "friction"], - cache=True, - ) - """ - If velocity falls between :attr:`pull_back_velocity` and - :attr:`terminal velocity` then the movement will start to coast - to the next coming stopping value. - - :attr:`terminal_velocity` is computed from a set formula given - :attr:`interval`, :attr:`coasting_alpha`, :attr:`pull_duration`, - and :attr:`friction`. Setting :attr:`terminal_velocity` has the - effect of setting :attr:`pull_duration`. - """ - - def start(self, val, t=None): - if self._anim: - self._anim.stop(self) - return ScrollEffect.start(self, val, t=t) - - def on_notch(self, *args): - return (self.scroll - self.anchor) % self.interval == 0 - - def nearest_notch(self, *args): - interval = float(self.interval) - anchor = self.anchor - n = round((self.scroll - anchor) / interval) - return anchor + n * interval - - def next_notch(self, *args): - interval = float(self.interval) - anchor = self.anchor - round_ = ceil if self.velocity > 0 else floor - n = round_((self.scroll - anchor) / interval) - return anchor + n * interval - - def near_notch(self, d=0.01): - nearest = self.nearest_notch() - if abs((nearest - self.scroll) / self.interval) % 1 < d: - return nearest - else: - return None - - def near_next_notch(self, d=None): - d = d or self.coasting_alpha - next_ = self.next_notch() - if abs((next_ - self.scroll) / self.interval) % 1 < d: - return next_ - else: - return None - - def update_velocity(self, dt): - if self.is_manual: - return - velocity = self.velocity - t_velocity = self.terminal_velocity - next_ = self.near_next_notch() - pull_back_velocity = self.pull_back_velocity - if pull_back_velocity < abs(velocity) < t_velocity and next_: - duration = abs((next_ - self.scroll) / self.velocity) - anim = Animation( - scroll=next_, - duration=duration, - ) - self._anim = anim - anim.on_complete = self._coasted_to_stop - anim.start(self) - return - if abs(velocity) < pull_back_velocity and not self.on_notch(): - anim = Animation( - scroll=self.nearest_notch(), - duration=self.pull_duration, - t="in_out_circ", - ) - self._anim = anim - anim.on_complete = self._coasted_to_stop - anim.start(self) - else: - self.velocity -= self.velocity * self.friction - self.apply_distance(self.velocity * dt) - self.trigger_velocity_update() - - def on_coasted_to_stop(self, *args): - """ - This event fires when the roulette has stopped, `making a selection`. - """ - - def _coasted_to_stop(self, *args): - self.velocity = 0 - self.dispatch("on_coasted_to_stop") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/__init__.cpython-311.pyc index 8ed4555..1c6c278 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/stiffscroll.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/stiffscroll.cpython-311.pyc index 149b3c0..4c67d31 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/stiffscroll.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/effects/stiffscroll/__pycache__/stiffscroll.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/factory_registers.py b/kivy_venv/lib/python3.11/site-packages/kivymd/factory_registers.py index 4497bfe..50fcd00 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/factory_registers.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/factory_registers.py @@ -7,11 +7,11 @@ from kivy.factory import Factory register = Factory.register register("MDSegmentedButton", module="kivymd.uix.segmentedbutton") register("MDSegmentedButtonItem", module="kivymd.uix.segmentedbutton") +register("MDSegmentButtonIcon", module="kivymd.uix.segmentedbutton") +register("MDSegmentButtonLabel", module="kivymd.uix.segmentedbutton") register("MDScrollView", module="kivymd.uix.scrollview") register("MDRecycleView", module="kivymd.uix.recycleview") register("MDResponsiveLayout", module="kivymd.uix.responsivelayout") -register("MDSegmentedControl", module="kivymd.uix.segmentedcontrol") -register("MDSegmentedControlItem", module="kivymd.uix.segmentedcontrol") register("MDSliverAppbar", module="kivymd.uix.sliverappbar") register("MDSliverAppbarContent", module="kivymd.uix.sliverappbar") register("MDSliverAppbarHeader", module="kivymd.uix.sliverappbar") @@ -19,8 +19,9 @@ register("MDNavigationRailItem", module="kivymd.uix.navigationrail") register("MDNavigationRail", module="kivymd.uix.navigationrail") register("MDNavigationRailFabButton", module="kivymd.uix.navigationrail") register("MDNavigationRailMenuButton", module="kivymd.uix.navigationrail") +register("MDNavigationRailItemIcon", module="kivymd.uix.navigationrail") +register("MDNavigationRailItemLabel", module="kivymd.uix.navigationrail") register("MDSwiper", module="kivymd.uix.swiper") -register("MDCarousel", module="kivymd.uix.carousel") register("MDWidget", module="kivymd.uix.widget") register("MDFloatLayout", module="kivymd.uix.floatlayout") register("MDAnchorLayout", module="kivymd.uix.anchorlayout") @@ -32,80 +33,102 @@ register("MDRelativeLayout", module="kivymd.uix.relativelayout") register("MDGridLayout", module="kivymd.uix.gridlayout") register("MDStackLayout", module="kivymd.uix.stacklayout") register("MDExpansionPanel", module="kivymd.uix.expansionpanel") -register("MDExpansionPanelOneLine", module="kivymd.uix.expansionpanel") -register("MDExpansionPanelTwoLine", module="kivymd.uix.expansionpanel") -register("MDExpansionPanelThreeLine", module="kivymd.uix.expansionpanel") +register("MDExpansionPanelHeader", module="kivymd.uix.expansionpanel") +register("MDExpansionPanelContent", module="kivymd.uix.expansionpanel") register("FitImage", module="kivymd.uix.fitimage") register("MDBackdrop", module="kivymd.uix.backdrop") -register("MDBanner", module="kivymd.uix.banner") register("MDTooltip", module="kivymd.uix.tooltip") +register("MDTooltipPlain", module="kivymd.uix.tooltip") +register("MDTooltipRich", module="kivymd.uix.tooltip") +register("MDTooltipRichActionButton", module="kivymd.uix.tooltip") +register("MDTooltipRichSubhead", module="kivymd.uix.tooltip") +register("MDTooltipRichSupportingText", module="kivymd.uix.tooltip") register("MDBottomSheet", module="kivymd.uix.bottomsheet") -register("MDBottomNavigation", module="kivymd.uix.bottomnavigation") -register("MDBottomNavigationItem", module="kivymd.uix.bottomnavigation") +register("MDBottomSheetDragHandle", module="kivymd.uix.bottomsheet") +register("MDBottomSheetDragHandleButton", module="kivymd.uix.bottomsheet") +register("MDBottomSheetDragHandleTitle", module="kivymd.uix.bottomsheet") +register("MDNavigationBar", module="kivymd.uix.navigationbar") +register("MDNavigationItem", module="kivymd.uix.navigationbar") +register("MDNavigationItemLabel", module="kivymd.uix.navigationbar") +register("MDNavigationItemIcon", module="kivymd.uix.navigationbar") register("MDToggleButton", module="kivymd.uix.behaviors.toggle_behavior") -register("MDFloatingActionButtonSpeedDial", module="kivymd.uix.button") +register("MDButton", module="kivymd.uix.button") +register("MDButtonText", module="kivymd.uix.button") +register("MDButtonIcon", module="kivymd.uix.button") +register("MDFabButton", module="kivymd.uix.button") register("MDIconButton", module="kivymd.uix.button") -register("MDRoundImageButton", module="kivymd.uix.button") -register("MDFlatButton", module="kivymd.uix.button") -register("MDRaisedButton", module="kivymd.uix.button") -register("MDFloatingActionButton", module="kivymd.uix.button") -register("MDRectangleFlatButton", module="kivymd.uix.button") -register("MDTextButton", module="kivymd.uix.button") -register("MDCustomRoundIconButton", module="kivymd.uix.button") -register("MDRoundFlatButton", module="kivymd.uix.button") -register("MDFillRoundFlatButton", module="kivymd.uix.button") -register("MDRectangleFlatIconButton", module="kivymd.uix.button") -register("MDRoundFlatIconButton", module="kivymd.uix.button") -register("MDFillRoundFlatIconButton", module="kivymd.uix.button") +register("MDExtendedFabButton", module="kivymd.uix.button") +register("MDExtendedFabButtonIcon", module="kivymd.uix.button") +register("MDExtendedFabButtonText", module="kivymd.uix.button") register("MDCard", module="kivymd.uix.card") -register("MDSeparator", module="kivymd.uix.card") -register("MDSelectionList", module="kivymd.uix.selection") +register("MDDivider", module="kivymd.uix.divider") register("MDChip", module="kivymd.uix.chip") +register("MDChipLeadingAvatar", module="kivymd.uix.chip") +register("MDChipLeadingIcon", module="kivymd.uix.chip") +register("MDChipTrailingIcon", module="kivymd.uix.chip") +register("MDChipText", module="kivymd.uix.chip") register("MDSmartTile", module="kivymd.uix.imagelist") +register("MDSmartTileOverlayContainer", module="kivymd.uix.imagelist") +register("MDSmartTileImage", module="kivymd.uix.imagelist") register("MDLabel", module="kivymd.uix.label") register("MDIcon", module="kivymd.uix.label") +register("MDBadge", module="kivymd.uix.badge") register("MDList", module="kivymd.uix.list") -register("ILeftBody", module="kivymd.uix.list") -register("ILeftBodyTouch", module="kivymd.uix.list") -register("IRightBody", module="kivymd.uix.list") -register("IRightBodyTouch", module="kivymd.uix.list") -register("OneLineListItem", module="kivymd.uix.list") -register("TwoLineListItem", module="kivymd.uix.list") -register("ThreeLineListItem", module="kivymd.uix.list") -register("OneLineAvatarListItem", module="kivymd.uix.list") -register("TwoLineAvatarListItem", module="kivymd.uix.list") -register("ThreeLineAvatarListItem", module="kivymd.uix.list") -register("OneLineIconListItem", module="kivymd.uix.list") -register("TwoLineIconListItem", module="kivymd.uix.list") -register("ThreeLineIconListItem", module="kivymd.uix.list") -register("OneLineRightIconListItem", module="kivymd.uix.list") -register("TwoLineRightIconListItem", module="kivymd.uix.list") -register("ThreeLineRightIconListItem", module="kivymd.uix.list") -register("OneLineAvatarIconListItem", module="kivymd.uix.list") -register("TwoLineAvatarIconListItem", module="kivymd.uix.list") -register("ThreeLineAvatarIconListItem", module="kivymd.uix.list") +register("MDListItem", module="kivymd.uix.list") +register("MDListItemHeadlineText", module="kivymd.uix.list") +register("MDListItemSupportingText", module="kivymd.uix.list") +register("MDListItemTrailingSupportingText", module="kivymd.uix.list") +register("MDListItemLeadingIcon", module="kivymd.uix.list") +register("MDListItemTrailingIcon", module="kivymd.uix.list") +register("MDListItemTrailingCheckbox", module="kivymd.uix.list") +register("MDListItemTertiaryText", module="kivymd.uix.list") register("HoverBehavior", module="kivymd.uix.behaviors.hover_behavior") register("FocusBehavior", module="kivymd.uix.behaviors.focus_behavior") register("MagicBehavior", module="kivymd.uix.behaviors.magic_behavior") register("MDNavigationDrawer", module="kivymd.uix.navigationdrawer") register("MDNavigationLayout", module="kivymd.uix.navigationdrawer") register("MDNavigationDrawerMenu", module="kivymd.uix.navigationdrawer") -register("MDNavigationDrawerHeader", module="kivymd.uix.navigationdrawer") register("MDNavigationDrawerItem", module="kivymd.uix.navigationdrawer") +register( + "MDNavigationDrawerItemLeadingIcon", module="kivymd.uix.navigationdrawer" +) +register( + "MDNavigationDrawerItemTrailingText", module="kivymd.uix.navigationdrawer" +) +register("MDNavigationDrawerHeader", module="kivymd.uix.navigationdrawer") +register("MDNavigationDrawerItemText", module="kivymd.uix.navigationdrawer") register("MDNavigationDrawerLabel", module="kivymd.uix.navigationdrawer") register("MDNavigationDrawerDivider", module="kivymd.uix.navigationdrawer") -register("MDProgressBar", module="kivymd.uix.progressbar") register("MDScrollViewRefreshLayout", module="kivymd.uix.refreshlayout") register("MDCheckbox", module="kivymd.uix.selectioncontrol") register("MDSwitch", module="kivymd.uix.selectioncontrol") register("MDSlider", module="kivymd.uix.slider") -register("MDSpinner", module="kivymd.uix.spinner") -register("MDTabs", module="kivymd.uix.tab") +register("MDCircularProgressIndicator", module="kivymd.uix.progressindicator") +register("MDLinearProgressIndicator", module="kivymd.uix.progressindicator") +register("MDTabsPrimary", module="kivymd.uix.tab") +register("MDTabsSecondary", module="kivymd.uix.tab") +register("MDTabsItem", module="kivymd.uix.tab") +register("MDTabsItemSecondary", module="kivymd.uix.tab") +register("MDTabsBadge", module="kivymd.uix.tab") +register("MDTabsItemIcon", module="kivymd.uix.tab") +register("MDTabsItemText", module="kivymd.uix.tab") +register("MDTabsCarousel", module="kivymd.uix.tab") register("MDTextField", module="kivymd.uix.textfield") -register("MDTextFieldRect", module="kivymd.uix.textfield") -register("MDTopAppBar", module="kivymd.uix.toolbar") -register("MDBottomAppBar", module="kivymd.uix.toolbar") +register("MDTextFieldHelperText", module="kivymd.uix.textfield") +register("MDTextFieldMaxLengthText", module="kivymd.uix.textfield") +register("MDTextFieldHintText", module="kivymd.uix.textfield") +register("MDTextFieldLeadingIcon", module="kivymd.uix.textfield") +register("MDTextFieldTrailingIcon", module="kivymd.uix.textfield") +register("MDTopAppBarTrailingButtonContainer", module="kivymd.uix.appbar") +register("MDTopAppBarLeadingButtonContainer", module="kivymd.uix.appbar") +register("MDFabBottomAppBarButton", module="kivymd.uix.appbar") +register("MDActionBottomAppBarButton", module="kivymd.uix.appbar") +register("MDTopAppBarTitle", module="kivymd.uix.appbar") +register("MDTopAppBar", module="kivymd.uix.appbar") +register("MDBottomAppBar", module="kivymd.uix.appbar") +register("MDActionTopAppBarButton", module="kivymd.uix.appbar") register("MDDropDownItem", module="kivymd.uix.dropdownitem") +register("MDDropDownItemText", module="kivymd.uix.dropdownitem") register("MDCircularLayout", module="kivymd.uix.circularlayout") register("MDHeroFrom", module="kivymd.uix.hero") register("MDHeroTo", module="kivymd.uix.hero") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/font_definitions.py b/kivy_venv/lib/python3.11/site-packages/kivymd/font_definitions.py index e7cc8e2..c3637e4 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/font_definitions.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/font_definitions.py @@ -1,5 +1,5 @@ """ -Themes/Font Definitions +Themes/Font definitions ======================= .. seealso:: @@ -8,6 +8,7 @@ Themes/Font Definitions """ from kivy.core.text import LabelBase +from kivy.metrics import sp from kivymd import fonts_path @@ -48,22 +49,102 @@ fonts = [ for font in fonts: LabelBase.register(**font) -theme_font_styles = [ - "H1", - "H2", - "H3", - "H4", - "H5", - "H6", - "Subtitle1", - "Subtitle2", - "Body1", - "Body2", - "Button", - "Caption", - "Overline", - "Icon", -] +# TODO: Add `weight` properties. +theme_font_styles = { + "Icon": { + "large": { + "line-height": 1, + "font-name": "Icons", + "font-size": sp(24), + }, + }, + "Display": { + "large": { + "line-height": 1.64, + "font-name": "Roboto", + "font-size": sp(57), + }, + "medium": { + "line-height": 1.52, + "font-name": "Roboto", + "font-size": sp(45), + }, + "small": { + "line-height": 1.44, + "font-name": "Roboto", + "font-size": sp(36), + }, + }, + "Headline": { + "large": { + "line-height": 1.40, + "font-name": "Roboto", + "font-size": sp(32), + }, + "medium": { + "line-height": 1.36, + "font-name": "Roboto", + "font-size": sp(28), + }, + "small": { + "line-height": 1.32, + "font-name": "Roboto", + "font-size": sp(24), + }, + }, + "Title": { + "large": { + "line-height": 1.28, + "font-name": "Roboto", + "font-size": sp(22), + }, + "medium": { + "line-height": 1.24, + "font-name": "Roboto", + "font-size": sp(16), + }, + "small": { + "line-height": 1.20, + "font-name": "Roboto", + "font-size": sp(14), + }, + }, + "Body": { + "large": { + "line-height": 1.24, + "font-name": "Roboto", + "font-size": sp(16), + }, + "medium": { + "line-height": 1.20, + "font-name": "Roboto", + "font-size": sp(14), + }, + "small": { + "line-height": 1.16, + "font-name": "Roboto", + "font-size": sp(12), + }, + }, + "Label": { + "large": { + "line-height": 1.20, + "font-name": "Roboto", + "font-size": sp(14), + }, + "medium": { + "line-height": 1.16, + "font-name": "Roboto", + "font-size": sp(12), + }, + "small": { + "line-height": 1.16, + "font-name": "Roboto", + "font-size": sp(11), + }, + }, +} """ -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles-2.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label-font-style-preview.png + :align: center """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Black.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Black.ttf index 689fe5c..0112e7d 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Black.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Black.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BlackItalic.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BlackItalic.ttf index 0b4e0ee..b2c6aca 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BlackItalic.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BlackItalic.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Bold.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Bold.ttf index d3f01ad..43da14d 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Bold.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Bold.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BoldItalic.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BoldItalic.ttf index 41cc1e7..bcfdab4 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BoldItalic.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-BoldItalic.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Italic.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Italic.ttf index 6a1cee5..1b5eaa3 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Italic.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Italic.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Light.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Light.ttf index 219063a..e7307e7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Light.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Light.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-LightItalic.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-LightItalic.ttf index 0e81e87..2d277af 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-LightItalic.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-LightItalic.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Medium.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Medium.ttf index 1a7f3b0..ac0f908 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Medium.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Medium.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-MediumItalic.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-MediumItalic.ttf index 0030295..fc36a47 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-MediumItalic.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-MediumItalic.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Regular.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Regular.ttf index 2c97eea..ddf4bfa 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Regular.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Regular.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Thin.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Thin.ttf index b74a4fd..2e0dee6 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Thin.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-Thin.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-ThinItalic.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-ThinItalic.ttf index dd0ddb8..084f9c0 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-ThinItalic.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/Roboto-ThinItalic.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/materialdesignicons-webfont.ttf b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/materialdesignicons-webfont.ttf index b00c684..bba7dcf 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/materialdesignicons-webfont.ttf and b/kivy_venv/lib/python3.11/site-packages/kivymd/fonts/materialdesignicons-webfont.ttf differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/icon_definitions.py b/kivy_venv/lib/python3.11/site-packages/kivymd/icon_definitions.py index 53cb540..a45366a 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/icon_definitions.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/icon_definitions.py @@ -2,17 +2,17 @@ Themes/Icon Definitions ======================= -.. seealso:: +.. seealso::Updateeeee - `Material Design Icons `_ + `Material Design Icons `_ -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/material-icons.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/material-m3-icons.png :align: center List of icons from materialdesignicons.com. These expanded material design icons are maintained by Austin Andrews (Templarian on Github). -LAST UPDATED: Version 7.2.96 +Version 7.4.47 To preview the icons and their names, you can use the following application: ---------------------------------------------------------------------------- @@ -21,25 +21,28 @@ To preview the icons and their names, you can use the following application: from kivy.lang import Builder from kivy.properties import StringProperty - from kivy.uix.screenmanager import Screen from kivymd.icon_definitions import md_icons + from kivymd.uix.screen import MDScreen from kivymd.app import MDApp - from kivymd.uix.list import OneLineIconListItem - + from kivymd.uix.list import MDListItem Builder.load_string( ''' #:import images_path kivymd.images_path - + - IconLeftWidget: + MDListItemLeadingIcon: icon: root.icon + MDListItemSupportingText: + text: root.text + + md_bg_color: self.theme_cls.backgroundColor MDBoxLayout: orientation: 'vertical' @@ -51,6 +54,7 @@ To preview the icons and their names, you can use the following application: MDIconButton: icon: 'magnify' + pos_hint: {'center_y': .5} MDTextField: id: search_field @@ -63,7 +67,7 @@ To preview the icons and their names, you can use the following application: key_size: 'height' RecycleBoxLayout: - padding: dp(10) + padding: dp(10), dp(10), 0, dp(10) default_size: None, dp(48) default_size_hint: 1, None size_hint_y: None @@ -73,19 +77,19 @@ To preview the icons and their names, you can use the following application: ) - class CustomOneLineIconListItem(OneLineIconListItem): + class IconItem(MDListItem): icon = StringProperty() + text = StringProperty() - class PreviousMDIcons(Screen): - + class PreviousMDIcons(MDScreen): def set_list_md_icons(self, text="", search=False): '''Builds a list of icons for the screen MDIcons.''' def add_icon_item(name_icon): self.ids.rv.data.append( { - "viewclass": "CustomOneLineIconListItem", + "viewclass": "IconItem", "icon": name_icon, "text": name_icon, "callback": lambda x: x, @@ -115,7 +119,7 @@ To preview the icons and their names, you can use the following application: MainApp().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icons-previous.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/m3-icons-previous.gif :align: center """ @@ -149,9 +153,12 @@ md_icons = { "account-badge": "\U000F1B0A", "account-badge-outline": "\U000F1B0B", "account-box": "\U000F0006", + "account-box-edit-outline": "\U000F1CC8", + "account-box-minus-outline": "\U000F1CC9", "account-box-multiple": "\U000F0934", "account-box-multiple-outline": "\U000F100A", "account-box-outline": "\U000F0007", + "account-box-plus-outline": "\U000F1CCA", "account-cancel": "\U000F12DF", "account-cancel-outline": "\U000F12E0", "account-card": "\U000F1BA4", @@ -181,6 +188,10 @@ md_icons = { "account-edit-outline": "\U000F0FFB", "account-eye": "\U000F0420", "account-eye-outline": "\U000F127B", + "account-file": "\U000F1CA7", + "account-file-outline": "\U000F1CA8", + "account-file-text": "\U000F1CA9", + "account-file-text-outline": "\U000F1CAA", "account-filter": "\U000F0936", "account-filter-outline": "\U000F0F9D", "account-group": "\U000F0849", @@ -669,6 +680,8 @@ md_icons = { "arrow-left-thin-circle-outline": "\U000F159A", "arrow-left-top": "\U000F17A7", "arrow-left-top-bold": "\U000F17A8", + "arrow-oscillating": "\U000F1C91", + "arrow-oscillating-off": "\U000F1C92", "arrow-projectile": "\U000F1840", "arrow-projectile-multiple": "\U000F183F", "arrow-right": "\U000F0054", @@ -840,6 +853,8 @@ md_icons = { "bag-personal-off": "\U000F0E11", "bag-personal-off-outline": "\U000F0E12", "bag-personal-outline": "\U000F0E13", + "bag-personal-plus": "\U000F1CA4", + "bag-personal-plus-outline": "\U000F1CA5", "bag-personal-tag": "\U000F1B0C", "bag-personal-tag-outline": "\U000F1B0D", "bag-suitcase": "\U000F158B", @@ -1145,10 +1160,12 @@ md_icons = { "book-off-outline": "\U000F1695", "book-open": "\U000F00BD", "book-open-blank-variant": "\U000F00BE", + "book-open-blank-variant-outline": "\U000F1CCB", "book-open-outline": "\U000F0B63", "book-open-page-variant": "\U000F05DA", "book-open-page-variant-outline": "\U000F15D6", "book-open-variant": "\U000F14F7", + "book-open-variant-outline": "\U000F1CCC", "book-outline": "\U000F0B64", "book-play": "\U000F0E82", "book-play-outline": "\U000F0E83", @@ -1339,9 +1356,11 @@ md_icons = { "bus-multiple": "\U000F0F3F", "bus-school": "\U000F079F", "bus-side": "\U000F07A0", + "bus-sign": "\U000F1CC1", "bus-stop": "\U000F1012", "bus-stop-covered": "\U000F1013", "bus-stop-uncovered": "\U000F1014", + "bus-wrench": "\U000F1CC2", "butterfly": "\U000F1589", "butterfly-outline": "\U000F158A", "button-cursor": "\U000F1B4F", @@ -1521,6 +1540,7 @@ md_icons = { "car-defrost-rear": "\U000F0D62", "car-door": "\U000F0B6B", "car-door-lock": "\U000F109D", + "car-door-lock-open": "\U000F1C81", "car-electric": "\U000F0B6C", "car-electric-outline": "\U000F15B5", "car-emergency": "\U000F160F", @@ -1639,6 +1659,7 @@ md_icons = { "cash-100": "\U000F0115", "cash-check": "\U000F14EE", "cash-clock": "\U000F1A91", + "cash-edit": "\U000F1CAB", "cash-fast": "\U000F185C", "cash-lock": "\U000F14EA", "cash-lock-open": "\U000F14EB", @@ -1702,6 +1723,7 @@ md_icons = { "chair-school": "\U000F0125", "chandelier": "\U000F1793", "charity": "\U000F0C4F", + "charity-search": "\U000F1C82", "chart-arc": "\U000F0126", "chart-areaspline": "\U000F0127", "chart-areaspline-variant": "\U000F0E91", @@ -1710,6 +1732,8 @@ md_icons = { "chart-bell-curve": "\U000F0C50", "chart-bell-curve-cumulative": "\U000F0FA7", "chart-box": "\U000F154D", + "chart-box-multiple": "\U000F1CCD", + "chart-box-multiple-outline": "\U000F1CCE", "chart-box-outline": "\U000F154E", "chart-box-plus-outline": "\U000F154F", "chart-bubble": "\U000F05E3", @@ -2026,6 +2050,8 @@ md_icons = { "cloud-cog-outline": "\U000F1BF1", "cloud-download": "\U000F0162", "cloud-download-outline": "\U000F0B7D", + "cloud-key": "\U000F1CA1", + "cloud-key-outline": "\U000F1CA2", "cloud-lock": "\U000F11F1", "cloud-lock-open": "\U000F1BF2", "cloud-lock-open-outline": "\U000F1BF3", @@ -2063,6 +2089,10 @@ md_icons = { "coach-lamp-variant": "\U000F1A37", "coat-rack": "\U000F109E", "code-array": "\U000F0168", + "code-block-braces": "\U000F1C83", + "code-block-brackets": "\U000F1C84", + "code-block-parentheses": "\U000F1C85", + "code-block-tags": "\U000F1C86", "code-braces": "\U000F0169", "code-braces-box": "\U000F10D6", "code-brackets": "\U000F016A", @@ -2501,6 +2531,7 @@ md_icons = { "diamond": "\U000F0B8A", "diamond-outline": "\U000F0B8B", "diamond-stone": "\U000F01C8", + "diaper-outline": "\U000F1CCF", "dice-1": "\U000F01CA", "dice-1-outline": "\U000F114A", "dice-2": "\U000F01CB", @@ -2584,6 +2615,7 @@ md_icons = { "donkey": "\U000F07C2", "door": "\U000F081A", "door-closed": "\U000F081B", + "door-closed-cancel": "\U000F1C93", "door-closed-lock": "\U000F10AF", "door-open": "\U000F081C", "door-sliding": "\U000F181E", @@ -2611,6 +2643,7 @@ md_icons = { "download-lock": "\U000F1320", "download-lock-outline": "\U000F1321", "download-multiple": "\U000F09E9", + "download-multiple-outline": "\U000F1CD0", "download-network": "\U000F06F4", "download-network-outline": "\U000F0C66", "download-off": "\U000F10B0", @@ -2643,7 +2676,10 @@ md_icons = { "earbuds-off-outline": "\U000F1851", "earbuds-outline": "\U000F1852", "earth": "\U000F01E7", + "earth-arrow-down": "\U000F1C87", + "earth-arrow-left": "\U000F1C88", "earth-arrow-right": "\U000F1311", + "earth-arrow-up": "\U000F1C89", "earth-box": "\U000F06CD", "earth-box-minus": "\U000F1407", "earth-box-off": "\U000F06CE", @@ -2747,11 +2783,17 @@ md_icons = { "emoticon-kiss-outline": "\U000F0C73", "emoticon-lol": "\U000F1214", "emoticon-lol-outline": "\U000F1215", + "emoticon-minus": "\U000F1CB2", + "emoticon-minus-outline": "\U000F1CB3", "emoticon-neutral": "\U000F0C74", "emoticon-neutral-outline": "\U000F01F6", "emoticon-outline": "\U000F01F2", + "emoticon-plus": "\U000F1CB4", + "emoticon-plus-outline": "\U000F1CB5", "emoticon-poop": "\U000F01F7", "emoticon-poop-outline": "\U000F0C75", + "emoticon-remove": "\U000F1CB6", + "emoticon-remove-outline": "\U000F1CB7", "emoticon-sad": "\U000F0C76", "emoticon-sad-outline": "\U000F01F8", "emoticon-sick": "\U000F157C", @@ -2781,6 +2823,7 @@ md_icons = { "ethernet": "\U000F0200", "ethernet-cable": "\U000F0201", "ethernet-cable-off": "\U000F0202", + "ethernet-off": "\U000F1CD1", "ev-plug-ccs1": "\U000F1519", "ev-plug-ccs2": "\U000F151A", "ev-plug-chademo": "\U000F151B", @@ -2811,6 +2854,7 @@ md_icons = { "eye-check-outline": "\U000F0D05", "eye-circle": "\U000F0B94", "eye-circle-outline": "\U000F0B95", + "eye-closed": "\U000F1CA3", "eye-lock": "\U000F1C06", "eye-lock-open": "\U000F1C07", "eye-lock-open-outline": "\U000F1C08", @@ -3090,6 +3134,7 @@ md_icons = { "fire-hydrant-alert": "\U000F1138", "fire-hydrant-off": "\U000F1139", "fire-off": "\U000F1722", + "fire-station": "\U000F1CC3", "fire-truck": "\U000F08AB", "firebase": "\U000F0967", "firefox": "\U000F0239", @@ -3531,6 +3576,8 @@ md_icons = { "gas-burner": "\U000F1A1B", "gas-cylinder": "\U000F0647", "gas-station": "\U000F0298", + "gas-station-in-use": "\U000F1CC4", + "gas-station-in-use-outline": "\U000F1CC5", "gas-station-off": "\U000F1409", "gas-station-off-outline": "\U000F140A", "gas-station-outline": "\U000F0EB8", @@ -3559,6 +3606,9 @@ md_icons = { "gender-male-female-variant": "\U000F113F", "gender-non-binary": "\U000F1140", "gender-transgender": "\U000F029F", + "generator-mobile": "\U000F1C8A", + "generator-portable": "\U000F1C8B", + "generator-stationary": "\U000F1C8C", "gentoo": "\U000F08E8", "gesture": "\U000F07CB", "gesture-double-tap": "\U000F073C", @@ -3794,6 +3844,7 @@ md_icons = { "heart-pulse": "\U000F05F6", "heart-remove": "\U000F1430", "heart-remove-outline": "\U000F1433", + "heart-search": "\U000F1C8D", "heart-settings": "\U000F1665", "heart-settings-outline": "\U000F1666", "heat-pump": "\U000F1A43", @@ -3918,7 +3969,10 @@ md_icons = { "hospital-building": "\U000F02E1", "hospital-marker": "\U000F02E2", "hot-tub": "\U000F0828", + "hours-12": "\U000F1C94", "hours-24": "\U000F1478", + "hub": "\U000F1C95", + "hub-outline": "\U000F1C96", "hubspot": "\U000F0D17", "hulu": "\U000F0829", "human": "\U000F02E6", @@ -3933,6 +3987,7 @@ md_icons = { "human-female-boy": "\U000F0A59", "human-female-dance": "\U000F15C9", "human-female-female": "\U000F0A5A", + "human-female-female-child": "\U000F1C8E", "human-female-girl": "\U000F0A5B", "human-greeting": "\U000F17C4", "human-greeting-proximity": "\U000F159D", @@ -3950,6 +4005,7 @@ md_icons = { "human-male-height": "\U000F0EFB", "human-male-height-variant": "\U000F0EFC", "human-male-male": "\U000F0A5E", + "human-male-male-child": "\U000F1C8F", "human-non-binary": "\U000F1848", "human-pregnant": "\U000F05CF", "human-queue": "\U000F1571", @@ -4063,6 +4119,59 @@ md_icons = { "integrated-circuit-chip": "\U000F1913", "invert-colors": "\U000F0301", "invert-colors-off": "\U000F0E4A", + "invoice": "\U000F1CD2", + "invoice-arrow-left": "\U000F1CD3", + "invoice-arrow-left-outline": "\U000F1CD4", + "invoice-arrow-right": "\U000F1CD5", + "invoice-arrow-right-outline": "\U000F1CD6", + "invoice-check": "\U000F1CD7", + "invoice-check-outline": "\U000F1CD8", + "invoice-clock": "\U000F1CD9", + "invoice-clock-outline": "\U000F1CDA", + "invoice-edit": "\U000F1CDB", + "invoice-edit-outline": "\U000F1CDC", + "invoice-export-outline": "\U000F1CDD", + "invoice-fast": "\U000F1CDE", + "invoice-fast-outline": "\U000F1CDF", + "invoice-import": "\U000F1CE0", + "invoice-import-outline": "\U000F1CE1", + "invoice-list": "\U000F1CE2", + "invoice-list-outline": "\U000F1CE3", + "invoice-minus": "\U000F1CE4", + "invoice-minus-outline": "\U000F1CE5", + "invoice-multiple": "\U000F1CE6", + "invoice-multiple-outline": "\U000F1CE7", + "invoice-outline": "\U000F1CE8", + "invoice-plus": "\U000F1CE9", + "invoice-plus-outline": "\U000F1CEA", + "invoice-remove": "\U000F1CEB", + "invoice-remove-outline": "\U000F1CEC", + "invoice-send": "\U000F1CED", + "invoice-send-outline": "\U000F1CEE", + "invoice-text": "\U000F1CEF", + "invoice-text-arrow-left": "\U000F1CF0", + "invoice-text-arrow-left-outline": "\U000F1CF1", + "invoice-text-arrow-right": "\U000F1CF2", + "invoice-text-arrow-right-outline": "\U000F1CF3", + "invoice-text-check": "\U000F1CF4", + "invoice-text-check-outline": "\U000F1CF5", + "invoice-text-clock": "\U000F1CF6", + "invoice-text-clock-outline": "\U000F1CF7", + "invoice-text-edit": "\U000F1CF8", + "invoice-text-edit-outline": "\U000F1CF9", + "invoice-text-fast": "\U000F1CFA", + "invoice-text-fast-outline": "\U000F1CFB", + "invoice-text-minus": "\U000F1CFC", + "invoice-text-minus-outline": "\U000F1CFD", + "invoice-text-multiple": "\U000F1CFE", + "invoice-text-multiple-outline": "\U000F1CFF", + "invoice-text-outline": "\U000F1D00", + "invoice-text-plus": "\U000F1D01", + "invoice-text-plus-outline": "\U000F1D02", + "invoice-text-remove": "\U000F1D03", + "invoice-text-remove-outline": "\U000F1D04", + "invoice-text-send": "\U000F1D05", + "invoice-text-send-outline": "\U000F1D06", "iobroker": "\U000F12E8", "ip": "\U000F0A5F", "ip-network": "\U000F0A60", @@ -4073,6 +4182,7 @@ md_icons = { "iron-board": "\U000F1838", "iron-outline": "\U000F1825", "island": "\U000F104F", + "island-variant": "\U000F1CC6", "iv-bag": "\U000F10B9", "jabber": "\U000F0DD5", "jeepney": "\U000F0302", @@ -4313,6 +4423,9 @@ md_icons = { "link-box-outline": "\U000F0D1B", "link-box-variant": "\U000F0D1C", "link-box-variant-outline": "\U000F0D1D", + "link-circle": "\U000F1CAC", + "link-circle-outline": "\U000F1CAD", + "link-edit": "\U000F1CAE", "link-lock": "\U000F10BA", "link-off": "\U000F0338", "link-plus": "\U000F0C94", @@ -4487,9 +4600,11 @@ md_icons = { "medication-outline": "\U000F1B15", "meditation": "\U000F117B", "memory": "\U000F035B", + "memory-arrow-down": "\U000F1CA6", "menorah": "\U000F17D4", "menorah-fire": "\U000F17D5", "menu": "\U000F035C", + "menu-close": "\U000F1C90", "menu-down": "\U000F035D", "menu-down-outline": "\U000F06B6", "menu-left": "\U000F035E", @@ -4694,10 +4809,16 @@ md_icons = { "motorbike-off": "\U000F1B16", "mouse": "\U000F037D", "mouse-bluetooth": "\U000F098B", + "mouse-left-click": "\U000F1D07", + "mouse-left-click-outline": "\U000F1D08", "mouse-move-down": "\U000F1550", "mouse-move-up": "\U000F1551", "mouse-move-vertical": "\U000F1552", "mouse-off": "\U000F037E", + "mouse-outline": "\U000F1D09", + "mouse-right-click": "\U000F1D0A", + "mouse-right-click-outline": "\U000F1D0B", + "mouse-scroll-wheel": "\U000F1D0C", "mouse-variant": "\U000F037F", "mouse-variant-off": "\U000F0380", "move-resize": "\U000F0655", @@ -5139,7 +5260,13 @@ md_icons = { "parking": "\U000F03E3", "party-popper": "\U000F1056", "passport": "\U000F07E3", + "passport-alert": "\U000F1CB8", "passport-biometric": "\U000F0DE1", + "passport-cancel": "\U000F1CB9", + "passport-check": "\U000F1CBA", + "passport-minus": "\U000F1CBB", + "passport-plus": "\U000F1CBC", + "passport-remove": "\U000F1CBD", "pasta": "\U000F1160", "patio-heater": "\U000F0F80", "patreon": "\U000F0882", @@ -5349,6 +5476,7 @@ md_icons = { "plus-network-outline": "\U000F0CBA", "plus-outline": "\U000F0705", "plus-thick": "\U000F11EC", + "pocket": "\U000F1CBE", "podcast": "\U000F0994", "podium": "\U000F0D25", "podium-bronze": "\U000F0D26", @@ -5478,6 +5606,7 @@ md_icons = { "progress-question": "\U000F1522", "progress-star": "\U000F1788", "progress-star-four-points": "\U000F1C3D", + "progress-tag": "\U000F1D0D", "progress-upload": "\U000F0998", "progress-wrench": "\U000F0CBD", "projector": "\U000F042E", @@ -5531,6 +5660,7 @@ md_icons = { "quality-high": "\U000F0435", "quality-low": "\U000F0A0C", "quality-medium": "\U000F0A0D", + "queue-first-in-last-out": "\U000F1CAF", "quora": "\U000F0D29", "rabbit": "\U000F0907", "rabbit-variant": "\U000F1A61", @@ -5779,6 +5909,7 @@ md_icons = { "rounded-corner": "\U000F0607", "router": "\U000F11E2", "router-network": "\U000F1087", + "router-network-wireless": "\U000F1C97", "router-wireless": "\U000F0469", "router-wireless-off": "\U000F15A3", "router-wireless-settings": "\U000F0A69", @@ -5923,10 +6054,14 @@ md_icons = { "serial-port": "\U000F065C", "server": "\U000F048B", "server-minus": "\U000F048C", + "server-minus-outline": "\U000F1C98", "server-network": "\U000F048D", "server-network-off": "\U000F048E", + "server-network-outline": "\U000F1C99", "server-off": "\U000F048F", + "server-outline": "\U000F1C9A", "server-plus": "\U000F0490", + "server-plus-outline": "\U000F1C9B", "server-remove": "\U000F0491", "server-security": "\U000F0492", "set-all": "\U000F0778", @@ -6466,6 +6601,7 @@ md_icons = { "swap-horizontal-bold": "\U000F0BCD", "swap-horizontal-circle": "\U000F0FE1", "swap-horizontal-circle-outline": "\U000F0FE2", + "swap-horizontal-hidden": "\U000F1D0E", "swap-horizontal-variant": "\U000F08C1", "swap-vertical": "\U000F04E2", "swap-vertical-bold": "\U000F0BCE", @@ -6563,6 +6699,8 @@ md_icons = { "tag-arrow-up-outline": "\U000F1732", "tag-check": "\U000F1A7A", "tag-check-outline": "\U000F1A7B", + "tag-edit": "\U000F1C9C", + "tag-edit-outline": "\U000F1C9D", "tag-faces": "\U000F04FA", "tag-heart": "\U000F068B", "tag-heart-outline": "\U000F0BCF", @@ -6838,6 +6976,7 @@ md_icons = { "traffic-light": "\U000F052B", "traffic-light-outline": "\U000F182A", "train": "\U000F052C", + "train-bus": "\U000F1CC7", "train-car": "\U000F0BD8", "train-car-autorack": "\U000F1B2D", "train-car-box": "\U000F1B2E", @@ -6931,6 +7070,8 @@ md_icons = { "truck-flatbed": "\U000F1891", "truck-minus": "\U000F19AE", "truck-minus-outline": "\U000F19BD", + "truck-off-road": "\U000F1C9E", + "truck-off-road-off": "\U000F1C9F", "truck-outline": "\U000F129D", "truck-plus": "\U000F19AD", "truck-plus-outline": "\U000F19BC", @@ -6975,6 +7116,7 @@ md_icons = { "umbrella-closed-outline": "\U000F13E2", "umbrella-closed-variant": "\U000F13E1", "umbrella-outline": "\U000F054B", + "underwear-outline": "\U000F1D0F", "undo": "\U000F054C", "undo-variant": "\U000F054D", "unfold-less-horizontal": "\U000F054E", @@ -6990,15 +7132,21 @@ md_icons = { "unreal": "\U000F09B1", "update": "\U000F06B0", "upload": "\U000F0552", + "upload-box": "\U000F1D10", + "upload-box-outline": "\U000F1D11", + "upload-circle": "\U000F1D12", + "upload-circle-outline": "\U000F1D13", "upload-lock": "\U000F1373", "upload-lock-outline": "\U000F1374", "upload-multiple": "\U000F083D", + "upload-multiple-outline": "\U000F1D14", "upload-network": "\U000F06F6", "upload-network-outline": "\U000F0CD8", "upload-off": "\U000F10C6", "upload-off-outline": "\U000F10C7", "upload-outline": "\U000F0E07", "usb": "\U000F0553", + "usb-c-port": "\U000F1CBF", "usb-flash-drive": "\U000F129E", "usb-flash-drive-outline": "\U000F129F", "usb-port": "\U000F11F0", @@ -7083,6 +7231,7 @@ md_icons = { "video-plus": "\U000F09B3", "video-plus-outline": "\U000F01D3", "video-stabilization": "\U000F091B", + "video-standard-definition": "\U000F1CA0", "video-switch": "\U000F0569", "video-switch-outline": "\U000F0790", "video-vintage": "\U000F0A1C", @@ -7233,6 +7382,9 @@ md_icons = { "watering-can-outline": "\U000F1482", "watermark": "\U000F0612", "wave": "\U000F0F2E", + "wave-arrow-down": "\U000F1CB0", + "wave-arrow-up": "\U000F1CB1", + "wave-undercurrent": "\U000F1CC0", "waveform": "\U000F147D", "waves": "\U000F078D", "waves-arrow-left": "\U000F1859", @@ -7251,6 +7403,9 @@ md_icons = { "weather-hurricane-outline": "\U000F1C78", "weather-lightning": "\U000F0593", "weather-lightning-rainy": "\U000F067E", + "weather-moonset": "\U000F1D15", + "weather-moonset-down": "\U000F1D16", + "weather-moonset-up": "\U000F1D17", "weather-night": "\U000F0594", "weather-night-partly-cloudy": "\U000F0F31", "weather-partly-cloudy": "\U000F0595", @@ -7424,23 +7579,27 @@ md_icons = { if __name__ == "__main__": from kivy.lang import Builder from kivy.properties import StringProperty - from kivy.uix.screenmanager import Screen + from kivymd.uix.screen import MDScreen from kivymd.app import MDApp - from kivymd.uix.list import OneLineIconListItem + from kivymd.uix.list import MDListItem Builder.load_string( """ #:import images_path kivymd.images_path - + - IconLeftWidget: + MDListItemLeadingIcon: icon: root.icon + MDListItemSupportingText: + text: root.text + + md_bg_color: self.theme_cls.backgroundColor MDBoxLayout: orientation: 'vertical' @@ -7452,6 +7611,7 @@ if __name__ == "__main__": MDIconButton: icon: 'magnify' + pos_hint: {'center_y': .5} MDTextField: id: search_field @@ -7464,7 +7624,7 @@ if __name__ == "__main__": key_size: 'height' RecycleBoxLayout: - padding: dp(10) + padding: dp(10), dp(10), 0, dp(10) default_size: None, dp(48) default_size_hint: 1, None size_hint_y: None @@ -7473,17 +7633,18 @@ if __name__ == "__main__": """ ) - class CustomOneLineIconListItem(OneLineIconListItem): + class IconItem(MDListItem): icon = StringProperty() + text = StringProperty() - class PreviousMDIcons(Screen): + class PreviousMDIcons(MDScreen): def set_list_md_icons(self, text="", search=False): """Builds a list of icons for the screen MDIcons.""" def add_icon_item(name_icon): self.ids.rv.data.append( { - "viewclass": "CustomOneLineIconListItem", + "viewclass": "IconItem", "icon": name_icon, "text": name_icon, "callback": lambda x: x, diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/images/alpha_layer.png b/kivy_venv/lib/python3.11/site-packages/kivymd/images/alpha_layer.png deleted file mode 100644 index 70cc2eb..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/images/alpha_layer.png and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/images/black.png b/kivy_venv/lib/python3.11/site-packages/kivymd/images/black.png deleted file mode 100644 index 0380795..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/images/black.png and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/images/blue.png b/kivy_venv/lib/python3.11/site-packages/kivymd/images/blue.png deleted file mode 100644 index b47c1e7..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/images/blue.png and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/images/green.png b/kivy_venv/lib/python3.11/site-packages/kivymd/images/green.png deleted file mode 100644 index 797f279..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/images/green.png and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/images/red.png b/kivy_venv/lib/python3.11/site-packages/kivymd/images/red.png deleted file mode 100644 index eac94b1..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/images/red.png and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/images/yellow.png b/kivy_venv/lib/python3.11/site-packages/kivymd/images/yellow.png deleted file mode 100644 index 6062231..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/images/yellow.png and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/material_resources.py b/kivy_venv/lib/python3.11/site-packages/kivymd/material_resources.py index 2eb6626..f2aeb31 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/material_resources.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/material_resources.py @@ -21,44 +21,3 @@ elif Window.width >= dp(600) and Window.height >= dp(600): DEVICE_TYPE = "tablet" else: DEVICE_TYPE = "mobile" - -if DEVICE_TYPE == "mobile": - MAX_NAV_DRAWER_WIDTH = dp(300) - HORIZ_MARGINS = dp(16) - STANDARD_INCREMENT = dp(56) - PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT - LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - dp(8) -else: - MAX_NAV_DRAWER_WIDTH = dp(400) - HORIZ_MARGINS = dp(24) - STANDARD_INCREMENT = dp(64) - PORTRAIT_TOOLBAR_HEIGHT = STANDARD_INCREMENT - LANDSCAPE_TOOLBAR_HEIGHT = STANDARD_INCREMENT - -# Elevation. -SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION = 1 -FILE_MANAGER_TOP_APP_BAR_ELEVATION = 1 -FLOATING_ACTION_BUTTON_M2_ELEVATION = 1 -FLOATING_ACTION_BUTTON_M3_ELEVATION = 0.5 -CARD_STYLE_ELEVATED_M3_ELEVATION = 0.5 -CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION = 0 -DATA_TABLE_ELEVATION = 4 -DROP_DOWN_MENU_ELEVATION = 2 -TOP_APP_BAR_ELEVATION = 2 -SNACK_BAR_ELEVATION = 2 - -# Shadow softness. -RAISED_BUTTON_SOFTNESS = 4 -FLOATING_ACTION_BUTTON_M3_SOFTNESS = 0 -DATA_TABLE_SOFTNESS = 12 -DROP_DOWN_MENU_SOFTNESS = 6 - -# Shadow offset. -RAISED_BUTTON_OFFSET = (0, -2) -FLOATING_ACTION_BUTTON_M2_OFFSET = (0, -1) -FLOATING_ACTION_BUTTON_M3_OFFSET = (0, -2) -DATA_TABLE_OFFSET = (0, -2) -DROP_DOWN_MENU_OFFSET = (0, -2) -SNACK_BAR_OFFSET = (0, -2) - -TOUCH_TARGET_HEIGHT = dp(48) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/theming.py b/kivy_venv/lib/python3.11/site-packages/kivymd/theming.py index 0c89bcb..5b4692f 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/theming.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/theming.py @@ -4,7 +4,7 @@ Themes/Theming .. seealso:: - `Material Design spec, Material theming `_ + `Material Design spec, Dynamic color `_ Material App ------------ @@ -21,231 +21,51 @@ Control material properties The main application class inherited from the :class:`~kivymd.app.MDApp` class has the :attr:`~kivymd.app.MDApp.theme_cls` attribute, with which you control the material properties of your application. - -Changing the theme colors -------------------------- - -The standard theme_cls is designed to provide the standard themes and colors as -defined by Material Design. - -We do not recommend that you change this. - -However, if you do need to change the standard colors, for instance to meet branding -guidelines, you can do this by overloading the `color_definitions.py` object. - -Create a custom color defintion object. This should have the same format as -the `colors `_ -object in `color_definitions.py` and contain definitions for at least the -primary color, the accent color and the Light and Dark backgrounds. - -.. note:: Your custom colors *must* use the names of the - `existing colors as defined in the palette `_ - e.g. You can have `Blue` but you cannot have `NavyBlue`. - -Add the custom theme to the :class:`~kivymd.app.MDApp` as shown in the -following snippet. - -.. tabs:: - - .. tab:: Imperative python style with KV - - .. code-block:: python - - from kivy.lang import Builder - from kivy.properties import ObjectProperty - - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - from kivymd.icon_definitions import md_icons - - colors = { - "Teal": { - "200": "#212121", - "500": "#212121", - "700": "#212121", - }, - "Red": { - "200": "#C25554", - "500": "#C25554", - "700": "#C25554", - }, - "Light": { - "StatusBar": "E0E0E0", - "AppBar": "#202020", - "Background": "#2E3032", - "CardsDialogs": "#FFFFFF", - "FlatButtonDown": "#CCCCCC", - }, - } - - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Custom theme" - - MDTabs: - id: tabs - - - - - MDIconButton: - id: icon - icon: root.icon - icon_size: "48sp" - theme_icon_color: "Custom" - icon_color: "white" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - icon = ObjectProperty() - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.colors = colors - self.theme_cls.primary_palette = "Teal" - self.theme_cls.accent_palette = "Red" - return Builder.load_string(KV) - - def on_start(self): - for name_tab in self.icons: - tab = Tab(title="This is " + name_tab, icon=name_tab) - self.root.ids.tabs.add_widget(tab) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivy.properties import ObjectProperty - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDIconButton - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.icon_definitions import md_icons - from kivymd.uix.toolbar import MDTopAppBar - - colors = { - "Teal": { - "200": "#212121", - "500": "#212121", - "700": "#212121", - }, - "Red": { - "200": "#C25554", - "500": "#C25554", - "700": "#C25554", - }, - "Light": { - "StatusBar": "E0E0E0", - "AppBar": "#202020", - "Background": "#2E3032", - "CardsDialogs": "#FFFFFF", - "FlatButtonDown": "#CCCCCC", - }, - } - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - icon = ObjectProperty() - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.colors = colors - self.theme_cls.primary_palette = "Teal" - self.theme_cls.accent_palette = "Red" - - return ( - MDBoxLayout( - MDTopAppBar(title="Custom theme"), - MDTabs(id="tabs"), - orientation="vertical", - ) - ) - - def on_start(self): - for name_tab in self.icons: - self.root.ids.tabs.add_widget( - Tab( - MDIconButton( - icon=name_tab, - icon_size="48sp", - theme_icon_color="Custom", - icon_color="white", - pos_hint={"center_x": .5, "center_y": .5}, - ), - title="This is " + name_tab, - icon=name_tab, - ) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-color.png - :align: center - -This will change the theme colors to your custom definition. In all other -respects, the theming stays as documented. - -.. warning:: Please note that the key ``'Red'`` is a required key for the - dictionary :attr:`kivymd.color_definition.colors`. """ -from kivy.animation import Animation +import os.path +from timeit import default_timer + from kivy.app import App -from kivy.clock import Clock +from kivy.logger import Logger from kivy.core.window import Window from kivy.event import EventDispatcher -from kivy.metrics import dp from kivy.properties import ( AliasProperty, BooleanProperty, - ColorProperty, DictProperty, NumericProperty, ObjectProperty, OptionProperty, StringProperty, ) -from kivy.utils import get_color_from_hex +from kivy import platform +from kivy.utils import get_color_from_hex, rgba, hex_colormap -from kivymd.color_definitions import colors, hue, palette +from kivymd.dynamic_color import DynamicColor from kivymd.font_definitions import theme_font_styles -from kivymd.material_resources import DEVICE_IOS, DEVICE_TYPE +from kivymd.material_resources import DEVICE_IOS + +from materialyoucolor.utils.color_utils import argb_from_rgba_01 +from materialyoucolor.dynamiccolor.material_dynamic_colors import ( + MaterialDynamicColors, +) +from materialyoucolor.utils.platform_utils import SCHEMES, get_dynamic_scheme +from materialyoucolor.hct import Hct +from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer -class ThemeManager(EventDispatcher): - primary_palette = OptionProperty("Blue", options=palette) +class ThemeManager(EventDispatcher, DynamicColor): + primary_palette = OptionProperty( + None, + options=[name_color.capitalize() for name_color in hex_colormap.keys()], + ) """ The name of the color scheme that the application will use. All major `material` components will have the color of the specified color theme. - Available options are: `'Red'`, `'Pink'`, `'Purple'`, `'DeepPurple'`, - `'Indigo'`, `'Blue'`, `'LightBlue'`, `'Cyan'`, `'Teal'`, `'Green'`, - `'LightGreen'`, `'Lime'`, `'Yellow'`, `'Amber'`, `'Orange'`, `'DeepOrange'`, - `'Brown'`, `'Gray'`, `'BlueGray'`. + See :attr:`kivy.utils.hex_colormap` keys for available values. To change the color scheme of an application: @@ -261,18 +81,24 @@ class ThemeManager(EventDispatcher): KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor - MDRectangleFlatButton: - text: "Hello, World" + MDButton: + style: "elevated" pos_hint: {"center_x": .5, "center_y": .5} + + MDButtonIcon: + icon: "plus" + + MDButtonText: + text: "Button" ''' class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Red" # "Purple", "Red" - + self.theme_cls.primary_palette = "Olive" # "Purple", "Red" return Builder.load_string(KV) @@ -283,350 +109,166 @@ class ThemeManager(EventDispatcher): .. code-block:: python from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton + from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText from kivymd.uix.screen import MDScreen class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" # "Purple", "Red" + self.theme_cls.primary_palette = "Olive" # "Purple", "Red" return ( MDScreen( - MDRectangleFlatButton( - text="Hello, World", + MDButton( + MDButtonIcon( + icon="plus", + ), + MDButtonText( + text="Button", + ), + style="elevated", pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) + ), + md_bg_color=self.theme_cls.backgroundColor, ) ) Example().run() - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-palette-m3.png :align: center :attr:`primary_palette` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Blue'`. + and defaults to `None`. """ - primary_hue = OptionProperty("500", options=hue) + dynamic_color_quality = NumericProperty(1 if platform == "android" else 10) """ - The color hue of the application. + The quality of the generated color scheme from the system wallpaper. + It is equal to or higher than `1`, with `1` representing the maximum quality. - Available options are: `'50'`, `'100'`, `'200'`, `'300'`, `'400'`, `'500'`, - `'600'`, `'700'`, `'800'`, `'900'`, `'A100'`, `'A200'`, `'A400'`, `'A700'`. + .. warning:: - To change the hue color scheme of an application: + Remember that by increasing the quality value, you also increase the + generation time of the color scheme. - .. tabs:: - - .. tab:: Imperative python style with KV - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton - - - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.primary_hue = "200" # "500" - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen - - - MainApp().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_hue = "200" # "500" - - return ( - MDScreen( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - ) - - - Example().run() - - With a value of ``self.theme_cls.primary_hue = "200"`` and ``self.theme_cls.primary_hue = "500"``: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary_hue.png - :align: center - - :attr:`primary_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'500'`. + :attr:`dynamic_color_quality` is an :class:`~kivy.properties.NumericProperty` + and defaults to `10` if platform is not Android else `1`. """ - primary_light_hue = OptionProperty("200", options=hue) + dynamic_color = BooleanProperty(False) """ - Hue value for :attr:`primary_light`. + Enables or disables dynamic color. - :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'200'`. - """ - - primary_dark_hue = OptionProperty("700", options=hue) - """ - Hue value for :attr:`primary_dark`. - - :attr:`primary_light_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'700'`. - """ - - def _get_primary_color(self) -> list: - return get_color_from_hex( - self.colors[self.primary_palette][self.primary_hue] - ) - - primary_color = AliasProperty( - _get_primary_color, bind=("primary_palette", "primary_hue") - ) - """ - The color of the current application theme. - - :attr:`primary_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value of the current application theme, property is readonly. - """ - - def _get_primary_light(self) -> list: - return get_color_from_hex( - self.colors[self.primary_palette][self.primary_light_hue] - ) - - primary_light = AliasProperty( - _get_primary_light, bind=("primary_palette", "primary_light_hue") - ) - """ - Colors of the current application color theme (in lighter color). - - .. tabs:: - - .. tab:: Declarative style with KV - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - - KV = ''' - MDScreen: - - MDRaisedButton: - text: "primary_light" - pos_hint: {"center_x": 0.5, "center_y": 0.7} - md_bg_color: app.theme_cls.primary_light - - MDRaisedButton: - text: "primary_color" - pos_hint: {"center_x": 0.5, "center_y": 0.5} - - MDRaisedButton: - text: "primary_dark" - pos_hint: {"center_x": 0.5, "center_y": 0.3} - md_bg_color: app.theme_cls.primary_dark - ''' - - - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - MainApp().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" - - return ( - MDScreen( - MDRaisedButton( - text="Primary light", - pos_hint={"center_x": 0.5, "center_y": 0.7}, - md_bg_color=self.theme_cls.primary_light, - ), - MDRaisedButton( - text="Primary color", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ), - MDRaisedButton( - text="Primary dark", - pos_hint={"center_x": 0.5, "center_y": 0.3}, - md_bg_color=self.theme_cls.primary_dark, - ), - ) - ) - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-colors-light-dark.png - :align: center - - :attr:`primary_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value of the current application theme (in lighter color), - property is readonly. - """ - - def _get_primary_dark(self) -> list: - return get_color_from_hex( - self.colors[self.primary_palette][self.primary_dark_hue] - ) - - primary_dark = AliasProperty( - _get_primary_dark, bind=("primary_palette", "primary_dark_hue") - ) - """ - Colors of the current application color theme (in darker color). - - :attr:`primary_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value of the current application theme (in darker color), - property is readonly. - """ - - accent_palette = OptionProperty("Amber", options=palette) - """ - The application color palette used for items such as the tab indicator - in the :class:`~kivymd.uix.tab.MDTabsBar` class and so on. - See :attr:`kivymd.uix.tab.MDTabsBar.indicator_color` attribute. - - :attr:`accent_palette` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Amber'`. - """ - - accent_hue = OptionProperty("500", options=hue) - """ - Similar to :attr:`primary_hue`, but returns a value for :attr:`accent_palette`. - - :attr:`accent_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'500'`. - """ - - accent_light_hue = OptionProperty("200", options=hue) - """ - Hue value for :attr:`accent_light`. - - :attr:`accent_light_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'200'`. - """ - - accent_dark_hue = OptionProperty("700", options=hue) - """ - Hue value for :attr:`accent_dark`. - - :attr:`accent_dark_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'700'`. - """ - - def _get_accent_color(self) -> list: - return get_color_from_hex( - self.colors[self.accent_palette][self.accent_hue] - ) - - accent_color = AliasProperty( - _get_accent_color, bind=["accent_palette", "accent_hue"] - ) - """ - Similar to :attr:`primary_color`, but returns a value for :attr:`accent_color`. - - :attr:`accent_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_color`, property is - readonly. - """ - - def _get_accent_light(self) -> list: - return get_color_from_hex( - self.colors[self.accent_palette][self.accent_light_hue] - ) - - accent_light = AliasProperty( - _get_accent_light, bind=["accent_palette", "accent_light_hue"] - ) - """ - Similar to :attr:`primary_light`, but returns a value for :attr:`accent_light`. - - :attr:`accent_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_light`, property is - readonly. - """ - - def _get_accent_dark(self) -> list: - return get_color_from_hex( - self.colors[self.accent_palette][self.accent_dark_hue] - ) - - accent_dark = AliasProperty( - _get_accent_dark, bind=["accent_palette", "accent_dark_hue"] - ) - """ - Similar to :attr:`primary_dark`, but returns a value for :attr:`accent_dark`. - - :attr:`accent_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`accent_dark`, property is - readonly. - """ - - material_style = OptionProperty("M3", options=["M2", "M3"]) - """ - Material design style. - Available options are: 'M2', 'M3'. - - .. versionadded:: 1.0.0 - - .. versionchanged:: 1.2.0 - By default now `'M3'`. + .. versionadded:: 2.0.0 .. seealso:: + + `Material Design spec, Dynamic color `_ - `Material Design 2 `_ and - `Material Design 3 `_ + To build the color scheme of your application from user wallpapers, you + must enable the `READ_EXTERNAL_STORAGE + `_ + permission if your android version is below 8.1: + + .. code-block:: python + + from kivy import platform + from kivy.lang import Builder + from kivy.clock import Clock + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: app.theme_cls.surfaceColor + + MDButton: + style: "elevated" + pos_hint: {"center_x": .5, "center_y": .5} + + MDButtonIcon: + icon: "plus" + + MDButtonText: + text: "Elevated" + ''' - :attr:`material_style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'M3'`. + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + def on_resume(self, *args): + '''Updating the color scheme when the application resumes.''' + + self.theme_cls.set_colors() + + def set_dynamic_color(self, *args) -> None: + ''' + When sets the `dynamic_color` value, the self method will be + `called.theme_cls.set_colors()` which will generate a color + scheme from a custom wallpaper if `dynamic_color` is `True`. + ''' + + self.theme_cls.dynamic_color = True + + def on_start(self) -> None: + ''' + It is fired at the start of the application and requests the + necessary permissions. + ''' + + def callback(permission, results): + if all([res for res in results]): + Clock.schedule_once(self.set_dynamic_color) + + if platform == "android": + from android.permissions import Permission, request_permissions + + permissions = [Permission.READ_EXTERNAL_STORAGE] + request_permissions(permissions, callback) + + + Example().run() + + :attr:`dynamic_color` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + dynamic_scheme_name = OptionProperty("TONAL_SPOT", options=SCHEMES.keys()) + """ + Name of the dynamic scheme. Availabe schemes `TONAL_SPOT`, `SPRITZ` + `VIBRANT`, `EXPRESSIVE`, `FRUIT_SALAD`, `RAINBOW`, `MONOCHROME`, `FIDELITY` + and `CONTENT`. + + :attr:`dynamic_scheme_name` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'TONAL_SPOT'`. """ - theme_style_switch_animation = BooleanProperty(False) + dynamic_scheme_contrast = NumericProperty(0.0) + """ + The contrast of the generated color scheme. + + :attr:`dynamic_scheme_contrast` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.0`. + """ + + path_to_wallpaper = StringProperty() + """ + The path to the image to set the color scheme. You can use this option + if you want to use dynamic color on platforms other than the Android + platform. + + .. versionadded:: 2.0.0 + + :attr:`path_to_wallpaper` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + + theme_style_switch_animation = BooleanProperty(True) """ Animate app colors when switching app color scheme ('Dark/light'). @@ -644,26 +286,29 @@ class ThemeManager(EventDispatcher): KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor MDCard: orientation: "vertical" padding: 0, 0, 0 , "36dp" size_hint: .5, .5 + style: "elevated" pos_hint: {"center_x": .5, "center_y": .5} - elevation: 2 - shadow_offset: 0, -2 MDLabel: text: "Theme style - {}".format(app.theme_cls.theme_style) halign: "center" valign: "center" bold: True - font_style: "H5" + font_style: "Display" + role: "small" - MDRaisedButton: - text: "Set theme" + MDButton: on_release: app.switch_theme_style() pos_hint: {"center_x": .5} + + MDButtonText: + text: "Set theme" ''' @@ -689,8 +334,10 @@ class ThemeManager(EventDispatcher): .. code-block:: python + from kivy.clock import Clock + from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton + from kivymd.uix.button import MDButton, MDButtonText from kivymd.uix.card import MDCard from kivymd.uix.label import MDLabel from kivymd.uix.screen import MDScreen @@ -706,14 +353,18 @@ class ThemeManager(EventDispatcher): MDCard( MDLabel( id="label", - text="Theme style - {}".format(self.theme_cls.theme_style), + text="Theme style - {}".format( + self.theme_cls.theme_style), halign="center", valign="center", bold=True, - font_style="H5", + font_style="Display", + role="small", ), - MDRaisedButton( - text="Set theme", + MDButton( + MDButtonText( + text="Set theme", + ), on_release=self.switch_theme_style, pos_hint={"center_x": 0.5}, ), @@ -722,12 +373,17 @@ class ThemeManager(EventDispatcher): padding=(0, 0, 0, "36dp"), size_hint=(0.5, 0.5), pos_hint={"center_x": 0.5, "center_y": 0.5}, - elevation=2, - shadow_offset=(0, -2), + style="elevated", ) ) ) + def on_start(self): + def on_start(*args): + self.root.md_bg_color = self.theme_cls.backgroundColor + + Clock.schedule_once(on_start) + def switch_theme_style(self, *args): self.theme_cls.primary_palette = ( "Orange" if self.theme_cls.primary_palette == "Red" else "Red" @@ -735,7 +391,7 @@ class ThemeManager(EventDispatcher): self.theme_cls.theme_style = ( "Dark" if self.theme_cls.theme_style == "Light" else "Light" ) - self.root.ids.card.ids.label.text = ( + self.root.get_ids().label.text = ( "Theme style - {}".format(self.theme_cls.theme_style) ) @@ -746,7 +402,7 @@ class ThemeManager(EventDispatcher): :align: center :attr:`theme_style_switch_animation` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. + and defaults to `True`. """ theme_style_switch_animation_duration = NumericProperty(0.2) @@ -774,58 +430,37 @@ class ThemeManager(EventDispatcher): """ App theme style. - .. tabs:: + .. code-block:: python - .. tab:: Imperative python style + from kivy.clock import Clock - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton + from kivymd.app import MDApp + from kivymd.uix.screen import MDScreen + from kivymd.uix.button import MDButton, MDButtonText - class MainApp(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" # "Light" - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Orange" + self.theme_cls.theme_style = "Light" # "Dark" + return MDScreen( + MDButton( + MDButtonText( + text="Hello, World", + ), + style="outlined", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ) + ) + + def on_start(self): + def on_start(*args): + self.root.md_bg_color = self.theme_cls.backgroundColor + + Clock.schedule_once(on_start) - MainApp().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.button import MDRectangleFlatButton - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" # "Light" - - return ( - MDScreen( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ), - ) - ) - - - Example().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/theme-style.png :align: center @@ -840,370 +475,6 @@ class ThemeManager(EventDispatcher): else: return self.theme_style - def _get_bg_darkest(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["StatusBar"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["StatusBar"]) - - bg_darkest = AliasProperty(_get_bg_darkest, bind=["theme_style"]) - """ - Similar to :attr:`bg_dark`, - but the color values are a tone lower (darker) than :attr:`bg_dark`. - - .. tabs:: - - .. tab:: Declarative style with KV - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDBoxLayout: - - MDWidget: - md_bg_color: app.theme_cls.bg_light - - MDBoxLayout: - md_bg_color: app.theme_cls.bg_normal - - MDBoxLayout: - md_bg_color: app.theme_cls.bg_dark - - MDBoxLayout: - md_bg_color: app.theme_cls.bg_darkest - ''' - - - class MainApp(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" # "Light" - return Builder.load_string(KV) - - - MainApp().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.widget import MDWidget - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" # "Light" - - return ( - MDBoxLayout( - MDWidget( - md_bg_color=self.theme_cls.bg_light, - ), - MDWidget( - md_bg_color=self.theme_cls.bg_normal, - ), - MDWidget( - md_bg_color=self.theme_cls.bg_dark, - ), - MDWidget( - md_bg_color=self.theme_cls.bg_darkest, - ), - ) - ) - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bg-normal-dark-darkest.png - :align: center - - :attr:`bg_darkest` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_darkest`, - property is readonly. - """ - - def _get_op_bg_darkest(self) -> list: - return self._get_bg_darkest(True) - - opposite_bg_darkest = AliasProperty( - _get_op_bg_darkest, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`bg_darkest`. - - :attr:`opposite_bg_darkest` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_bg_darkest`, - property is readonly. - """ - - def _get_bg_dark(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["AppBar"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["AppBar"]) - - bg_dark = AliasProperty(_get_bg_dark, bind=["theme_style"]) - """ - Similar to :attr:`bg_normal`, - but the color values are one tone lower (darker) than :attr:`bg_normal`. - - :attr:`bg_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_dark`, - property is readonly. - """ - - def _get_op_bg_dark(self) -> list: - return self._get_bg_dark(True) - - opposite_bg_dark = AliasProperty(_get_op_bg_dark, bind=["theme_style"]) - """ - The opposite value of color in the :attr:`bg_dark`. - - :attr:`opposite_bg_dark` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`opposite_bg_dark`, - property is readonly. - """ - - def _get_bg_normal(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["Background"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["Background"]) - - bg_normal = AliasProperty(_get_bg_normal, bind=["theme_style"]) - """ - Similar to :attr:`bg_light`, - but the color values are one tone lower (darker) than :attr:`bg_light`. - - :attr:`bg_normal` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_normal`, - property is readonly. - """ - - def _get_op_bg_normal(self) -> list: - return self._get_bg_normal(True) - - opposite_bg_normal = AliasProperty(_get_op_bg_normal, bind=["theme_style"]) - """ - The opposite value of color in the :attr:`bg_normal`. - - :attr:`opposite_bg_normal` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_bg_normal`, - property is readonly. - """ - - def _get_bg_light(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - return get_color_from_hex(self.colors["Light"]["CardsDialogs"]) - elif theme_style == "Dark": - return get_color_from_hex(self.colors["Dark"]["CardsDialogs"]) - - bg_light = AliasProperty(_get_bg_light, bind=["theme_style"]) - """" - Depending on the style of the theme (`'Dark'` or `'Light`') - that the application uses, :attr:`bg_light` contains the color value - in ``rgba`` format for the widgets background. - - :attr:`bg_light` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`bg_light`, - property is readonly. - """ - - def _get_op_bg_light(self) -> list: - return self._get_bg_light(True) - - opposite_bg_light = AliasProperty(_get_op_bg_light, bind=["theme_style"]) - """ - The opposite value of color in the :attr:`bg_light`. - - :attr:`opposite_bg_light` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_bg_light`, - property is readonly. - """ - - def _get_divider_color(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - color[3] = 0.12 - return color - - divider_color = AliasProperty(_get_divider_color, bind=["theme_style"]) - """ - Color for dividing lines such as :class:`~kivymd.uix.card.MDSeparator`. - - :attr:`divider_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`divider_color`, - property is readonly. - """ - - def _get_op_divider_color(self) -> list: - return self._get_divider_color(True) - - opposite_divider_color = AliasProperty( - _get_op_divider_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`divider_color`. - - :attr:`opposite_divider_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_divider_color`, - property is readonly. - """ - - def _get_disabled_primary_color(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - lum = sum(self.primary_color[0:3]) / 3.0 - if theme_style == "Light": - a = 0.38 - elif theme_style == "Dark": - a = 0.50 - return [lum, lum, lum, a] - - disabled_primary_color = AliasProperty( - _get_disabled_primary_color, bind=["theme_style"] - ) - """ - The greyscale disabled version of the current application theme color - in ``rgba`` format. - - .. versionadded:: 1.0.0 - - :attr:`disabled_primary_color` - is an :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`disabled_primary_color`, - property is readonly. - """ - - def _get_op_disabled_primary_color(self) -> list: - return self._get_disabled_primary_color(True) - - opposite_disabled_primary_color = AliasProperty( - _get_op_disabled_primary_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`disabled_primary_color`. - - .. versionadded:: 1.0.0 - - :attr:`opposite_disabled_primary_color` is an - :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`opposite_disabled_primary_color`, - property is readonly. - """ - - def _get_text_color(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.87 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - return color - - text_color = AliasProperty(_get_text_color, bind=["theme_style"]) - """ - Color of the text used in the :class:`~kivymd.uix.label.MDLabel`. - - :attr:`text_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`text_color`, - property is readonly. - """ - - def _get_op_text_color(self) -> list: - return self._get_text_color(True) - - opposite_text_color = AliasProperty( - _get_op_text_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`text_color`. - - :attr:`opposite_text_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_text_color`, - property is readonly. - """ - - def _get_secondary_text_color(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.54 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - color[3] = 0.70 - return color - - secondary_text_color = AliasProperty( - _get_secondary_text_color, bind=["theme_style"] - ) - """ - The color for the secondary text that is used in classes - from the module :class:`~kivymd/uix/list.TwoLineListItem`. - - :attr:`secondary_text_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`secondary_text_color`, - property is readonly. - """ - - def _get_op_secondary_text_color(self) -> list: - return self._get_secondary_text_color(True) - - opposite_secondary_text_color = AliasProperty( - _get_op_secondary_text_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`secondary_text_color`. - - :attr:`opposite_secondary_text_color` - is an :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`opposite_secondary_text_color`, - property is readonly. - """ - - def _get_icon_color(self, opposite: bool = False) -> list: - theme_style = self._get_theme_style(opposite) - if theme_style == "Light": - color = get_color_from_hex("000000") - color[3] = 0.54 - elif theme_style == "Dark": - color = get_color_from_hex("FFFFFF") - return color - - icon_color = AliasProperty(_get_icon_color, bind=["theme_style"]) - """ - Color of the icon used in the :class:`~kivymd.uix.button.MDIconButton`. - - :attr:`icon_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`icon_color`, - property is readonly. - """ - - def _get_op_icon_color(self) -> list: - return self._get_icon_color(True) - - opposite_icon_color = AliasProperty( - _get_op_icon_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`icon_color`. - - :attr:`opposite_icon_color` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`opposite_icon_color`, - property is readonly. - """ - def _get_disabled_hint_text_color(self, opposite: bool = False) -> list: theme_style = self._get_theme_style(opposite) if theme_style == "Light": @@ -1226,146 +497,22 @@ class ThemeManager(EventDispatcher): property is readonly. """ - def _get_op_disabled_hint_text_color(self) -> list: - return self._get_disabled_hint_text_color(True) - - opposite_disabled_hint_text_color = AliasProperty( - _get_op_disabled_hint_text_color, bind=["theme_style"] - ) - """ - The opposite value of color in the :attr:`disabled_hint_text_color`. - - :attr:`opposite_disabled_hint_text_color` - is an :class:`~kivy.properties.AliasProperty` that returns the value - in ``rgba`` format for :attr:`opposite_disabled_hint_text_color`, - property is readonly. - """ - - # Hardcoded because muh standard - def _get_error_color(self) -> list: - return get_color_from_hex(self.colors["Red"]["A700"]) - - error_color = AliasProperty(_get_error_color, bind=["theme_style"]) - """ - Color of the error text used - in the :class:`~kivymd.uix.textfield.MDTextField`. - - :attr:`error_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`error_color`, - property is readonly. - """ - - def _get_ripple_color(self) -> list: - return self._ripple_color - - def _set_ripple_color(self, value) -> None: - self._ripple_color = value - - _ripple_color = ColorProperty(colors["Gray"]["400"]) - """Private value.""" - - ripple_color = AliasProperty( - _get_ripple_color, _set_ripple_color, bind=["_ripple_color"] - ) - """ - Color of ripple effects. - - :attr:`ripple_color` is an :class:`~kivy.properties.AliasProperty` that - returns the value in ``rgba`` format for :attr:`ripple_color`, - property is readonly. - """ - def _determine_device_orientation(self, _, window_size) -> None: if window_size[0] > window_size[1]: self.device_orientation = "landscape" elif window_size[1] >= window_size[0]: self.device_orientation = "portrait" - device_orientation = StringProperty("") + device_orientation = StringProperty() """ Device orientation. - :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty`. + :attr:`device_orientation` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. """ - def _get_standard_increment(self) -> float: - if DEVICE_TYPE == "mobile": - if self.device_orientation == "landscape": - return dp(48) - else: - return dp(56) - else: - return dp(64) - - standard_increment = AliasProperty( - _get_standard_increment, bind=["device_orientation"] - ) - """ - Value of standard increment. - - :attr:`standard_increment` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`standard_increment`, - property is readonly. - """ - - def _get_horizontal_margins(self) -> float: - if DEVICE_TYPE == "mobile": - return dp(16) - else: - return dp(24) - - horizontal_margins = AliasProperty(_get_horizontal_margins) - """ - Value of horizontal margins. - - :attr:`horizontal_margins` is an :class:`~kivy.properties.AliasProperty` - that returns the value in ``rgba`` format for :attr:`horizontal_margins`, - property is readonly. - """ - - def on_theme_style(self, interval: int, theme_style: str) -> None: - if ( - hasattr(App.get_running_app(), "theme_cls") - and App.get_running_app().theme_cls == self - ): - self.set_clearcolor_by_theme_style(theme_style) - - _set_clearcolor = False - - def set_clearcolor_by_theme_style(self, theme_style): - if self.theme_style_switch_animation and self._set_clearcolor: - Animation( - clearcolor=get_color_from_hex( - self.colors[theme_style]["Background"] - ), - d=self.theme_style_switch_animation_duration, - t="linear", - ).start(Window) - else: - Window.clearcolor = get_color_from_hex( - self.colors[theme_style]["Background"] - ) - self._set_clearcolor = True - # Font name, size (sp), always caps, letter spacing (sp). - font_styles = DictProperty( - { - "H1": ["RobotoLight", 96, False, -1.5], - "H2": ["RobotoLight", 60, False, -0.5], - "H3": ["Roboto", 48, False, 0], - "H4": ["Roboto", 34, False, 0.25], - "H5": ["Roboto", 24, False, 0], - "H6": ["RobotoMedium", 20, False, 0.15], - "Subtitle1": ["Roboto", 16, False, 0.15], - "Subtitle2": ["RobotoMedium", 14, False, 0.1], - "Body1": ["Roboto", 16, False, 0.5], - "Body2": ["Roboto", 14, False, 0.25], - "Button": ["RobotoMedium", 14, True, 1.25], - "Caption": ["Roboto", 12, False, 0.4], - "Overline": ["Roboto", 10, True, 1.5], - "Icon": ["Icons", 24, False, 0], - } - ) + font_styles = DictProperty(theme_font_styles) """ Data of default font styles. @@ -1380,184 +527,167 @@ class ThemeManager(EventDispatcher): from kivy.core.text import LabelBase from kivy.lang import Builder + from kivy.metrics import sp from kivymd.app import MDApp - from kivymd.font_definitions import theme_font_styles KV = ''' MDScreen: - + md_bg_color: self.theme_cls.backgroundColor + MDLabel: - text: "JetBrainsMono" + text: "MDLabel" halign: "center" - font_style: "JetBrainsMono" + font_style: "nasalization" ''' - class MainApp(MDApp): + class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" LabelBase.register( - name="JetBrainsMono", - fn_regular="JetBrainsMono-Regular.ttf") + name="nasalization", + fn_regular="nasalization.ttf", + ) + + self.theme_cls.font_styles["nasalization"] = { + "large": { + "line-height": 1.64, + "font-name": "nasalization", + "font-size": sp(57), + }, + "medium": { + "line-height": 1.52, + "font-name": "nasalization", + "font-size": sp(45), + }, + "small": { + "line-height": 1.44, + "font-name": "nasalization", + "font-size": sp(36), + }, + } - theme_font_styles.append('JetBrainsMono') - self.theme_cls.font_styles["JetBrainsMono"] = [ - "JetBrainsMono", - 16, - False, - 0.15, - ] return Builder.load_string(KV) - MainApp().run() + Example().run() .. tab:: Declarative python style .. code-block:: python from kivy.core.text import LabelBase + from kivy.metrics import sp - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen from kivymd.uix.label import MDLabel - from kivymd.font_definitions import theme_font_styles + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp - class MainApp(MDApp): + class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" LabelBase.register( - name="JetBrainsMono", - fn_regular="JetBrainsMono-Regular.ttf") + name="nasalization", + fn_regular="/Users/urijivanov/Projects/Dev/MyGithub/Articles/StarTest/data/font/nasalization-rg.ttf", + ) + + self.theme_cls.font_styles["nasalization"] = { + "large": { + "line-height": 1.64, + "font-name": "nasalization", + "font-size": sp(57), + }, + "medium": { + "line-height": 1.52, + "font-name": "nasalization", + "font-size": sp(45), + }, + "small": { + "line-height": 1.44, + "font-name": "nasalization", + "font-size": sp(36), + }, + } - theme_font_styles.append('JetBrainsMono') - self.theme_cls.font_styles["JetBrainsMono"] = [ - "JetBrainsMono", - 16, - False, - 0.15, - ] return ( MDScreen( MDLabel( text="JetBrainsMono", halign="center", - font_style="JetBrainsMono", + font_style="nasalization", ) ) ) - MainApp().run() + Example().run() - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-styles.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-font-styles.png :align: center :attr:`font_styles` is an :class:`~kivy.properties.DictProperty`. """ - def set_colors( - self, - primary_palette: str, - primary_hue: str, - primary_light_hue: str, - primary_dark_hue: str, - accent_palette: str, - accent_hue: str, - accent_light_hue: str, - accent_dark_hue: str, - ) -> None: - """ - Courtesy method to allow all of the theme color attributes to be set in one call. + on_colors = None + """ + A Helper function called when colors are changed. - :attr:`set_colors` allows all of the following to be set in one method call: + :attr: `on_colors` defaults to `None`. + """ - * primary palette color, - * primary hue, - * primary light hue, - * primary dark hue, - * accent palette color, - * accent hue, - * accent ligth hue, and - * accent dark hue. - - Note that all values *must* be provided. If you only want to set one or two values - use the appropriate method call for that. - - .. tabs:: - - .. tab:: Imperative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton - - class MainApp(MDApp): - def build(self): - self.theme_cls.set_colors( - "Blue", "600", "50", "800", "Teal", "600", "100", "800" - ) - screen = MDScreen() - screen.add_widget( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - return screen - - - MainApp().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatButton - - class MainApp(MDApp): - def build(self): - self.theme_cls.set_colors( - "Blue", "600", "50", "800", "Teal", "600", "100", "800" - ) - return ( - MDScreen( - MDRectangleFlatButton( - text="Hello, World", - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - ) - - - MainApp().run() - """ - - self.primary_palette = primary_palette - self.primary_hue = primary_hue - self.primary_light_hue = primary_light_hue - self.primary_dark_hue = primary_dark_hue - self.accent_palette = accent_palette - self.accent_hue = accent_hue - self.accent_light_hue = accent_light_hue - self.accent_dark_hue = accent_dark_hue + _size_current_wallpaper = NumericProperty(0) + _dark_mode = lambda self : False if self.theme_style == "Light" else True def __init__(self, **kwargs): super().__init__(**kwargs) - Clock.schedule_once(lambda x: self.on_theme_style(0, self.theme_style)) self._determine_device_orientation(None, Window.size) Window.bind(size=self._determine_device_orientation) - self.bind(font_styles=self.sync_theme_styles) - self.colors = colors - Clock.schedule_once(self.sync_theme_styles) + + def set_colors(self, *args) -> None: + """Fired methods for setting a new color scheme.""" + + if not self.dynamic_color: + if not self.primary_palette: + self._set_application_scheme() + else: + self._set_palette_color() + else: + system_scheme = get_dynamic_scheme( + dark_mode=self._dark_mode(), + contrast=self.dynamic_scheme_contrast, + dynamic_color_quality=self.dynamic_color_quality, + fallback_wallpaper_path=self.path_to_wallpaper, + fallback_scheme_name=self.dynamic_scheme_name, + message_logger=Logger.info, + logger_head="KivyMD" + ) + if system_scheme: + self._set_color_names(system_scheme) + else: + self._set_application_scheme() + + def update_theme_colors(self, *args) -> None: + """Fired when the `theme_style` value changes.""" + + self.set_colors() + + def on_dynamic_scheme_name(self, *args): + self.set_colors() + + def on_dynamic_scheme_contrast(self, *args): + self.set_colors() + + def on_path_to_wallpaper(self, *args): + self.set_colors() + + def switch_theme(self) -> None: + """Switches the theme from light to dark.""" + + self.theme_style = "Dark" if self.theme_style == "Light" else "Light" def sync_theme_styles(self, *args) -> None: # Syncs the values from self.font_styles to theme_font_styles @@ -1568,6 +698,41 @@ class ThemeManager(EventDispatcher): for style in self.font_styles.keys(): theme_font_styles.append(style) + def _set_application_scheme( + self, + color = "blue", # Google default + ) -> None: + if not color: + color = "blue" + + color = get_color_from_hex(hex_colormap[color.lower()]) + color = Hct.from_int(argb_from_rgba_01(color)) + color = DislikeAnalyzer.fix_if_disliked(color).to_int() + + self._set_color_names( + SCHEMES[self.dynamic_scheme_name]( + Hct.from_int(color), + self._dark_mode(), + self.dynamic_scheme_contrast, + ) + ) + + def _set_color_names(self, scheme) -> None: + for color_name in vars(MaterialDynamicColors).keys(): + attr = getattr(MaterialDynamicColors, color_name) + if hasattr(attr, "get_hct"): + color_value = rgba(attr.get_hct(scheme).to_rgba()) + exec(f"self.{color_name}Color = {color_value}") + + self.disabledTextColor = self._get_disabled_hint_text_color() + if self.on_colors: + self.on_colors() + + def _set_palette_color(self) -> None: + if not self.primary_palette: + self.primary_palette = "Blue" + self._set_application_scheme(self.primary_palette) + class ThemableBehavior(EventDispatcher): theme_cls = ObjectProperty() @@ -1584,72 +749,214 @@ class ThemableBehavior(EventDispatcher): :attr:`device_ios` is an :class:`~kivy.properties.BooleanProperty`. """ - widget_style = OptionProperty( - "android", options=["android", "ios", "desktop"] + theme_line_color = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Line color scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_line_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_bg_color = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Background color scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_bg_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_shadow_color = OptionProperty( + "Primary", options=["Primary", "Custom"] ) """ - Allows to set one of the three style properties for the widget: - `'android'`, `'ios'`, `'desktop'`. + Elevation color scheme name. - For example, for the class :class:`~kivymd.uix.selectioncontrol.MDSwitch` - has two styles - `'android'` and `'ios'`: + .. versionadded:: 2.0.0 - .. code-block:: kv + Available options are: `'Primary'`, `'Custom'`. - MDSwitch: - widget_style: "ios" - - .. code-block:: kv - - MDSwitch: - widget_style: "android" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switch-android-ios.png - :align: center - - :attr:`widget_style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'android'`. + :attr:`theme_shadow_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. """ - opposite_colors = BooleanProperty(False) + theme_shadow_offset = OptionProperty( + "Primary", options=["Primary", "Custom"] + ) """ - For some widgets, for example, for a widget - :class:`~kivymd.uix.toolbar.MDTopAppBar` changes the color of the label to - the color opposite to the main theme. + Elevation offset scheme name. - .. code-block:: kv + .. versionadded:: 2.0.0 - MDTopAppBar: - title: "MDTopAppBar" - opposite_colors: True + Available options are: `'Primary'`, `'Custom'`. - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-true.png - :align: center + :attr:`theme_shadow_offset` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ - .. code-block:: kv + theme_elevation_level = OptionProperty( + "Primary", options=["Primary", "Custom"] + ) + """ + Elevation level scheme name. - MDTopAppBar: - title: "MDTopAppBar" - opposite_colors: True + .. versionadded:: 2.0.0 - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-false.png - :align: center + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_elevation_level` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_font_size = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Font size scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_font_size` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_width = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Widget width scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_width` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_height = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Widget width scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_height` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_line_height = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Line height scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_line_height` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_font_name = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Font name scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_font_name` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_shadow_softness = OptionProperty( + "Primary", options=["Primary", "Custom"] + ) + """ + Elevation softness scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_shadow_softness` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_focus_color = OptionProperty("Primary", options=["Primary", "Custom"]) + """ + Focus color scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_focus_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_divider_color = OptionProperty( + "Primary", options=["Primary", "Custom"] + ) + """ + Divider color scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_divider_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_text_color = OptionProperty( + "Primary", + options=[ + "Primary", + "Secondary", + "Hint", + "Error", + "Custom", + ], + ) + """ + Label color scheme name. + + Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`, + `'Custom'`. + + :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. + """ + + theme_icon_color = OptionProperty( + "Primary", + options=[ + "Primary", + "Secondary", + "Hint", + "Error", + "Custom", + ], + ) + """ + Label color scheme name. + + Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`, + `'Custom'`. + + :attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Primary'`. """ def __init__(self, **kwargs): - self.unbind_properties = [ - "theme_style", - "material_style", - "device_orientation", - "primary_color", - "primary_palette", - "accent_palette", - "text_color", - ] - - if self.theme_cls is not None: - pass - else: + if self.theme_cls is None: try: if not isinstance( App.get_running_app().property("theme_cls", True), @@ -1690,15 +997,7 @@ class ThemableBehavior(EventDispatcher): if hasattr(callback, "proxy") and hasattr( callback.proxy, "theme_cls" ): - if issubclass(widget.__class__, self.md_textfield): - widget.theme_cls.unbind( - **{ - "theme_style": getattr( - callback.proxy, callback.method_name - ) - } - ) - for property_name in self.unbind_properties: + for property_name in ["theme_style", "primary_palette"]: if widget == callback.proxy: widget.theme_cls.unbind( **{ @@ -1707,10 +1006,6 @@ class ThemableBehavior(EventDispatcher): ) } ) - # KivyMD widgets may contain other MD widgets. - for children in widget.children: - if hasattr(children, "theme_cls"): - self.remove_widget(children) except ReferenceError: pass diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/theming_dynamic_text.py b/kivy_venv/lib/python3.11/site-packages/kivymd/theming_dynamic_text.py deleted file mode 100644 index 64f9921..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/theming_dynamic_text.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Theming Dynamic Text -==================== - -Two implementations. The first is based on color brightness obtained from- -https://www.w3.org/TR/AERT#color-contrast -The second is based on relative luminance calculation for sRGB obtained from- -https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef -and contrast ratio calculation obtained from- -https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef - -Preliminary testing suggests color brightness more closely matches the -`Material Design spec` suggested text colors, but the alternative implementation -is both newer and the current 'correct' recommendation, so is included here -as an option. -""" - - -def _color_brightness(color): - # Implementation of color brightness method - brightness = color[0] * 299 + color[1] * 587 + color[2] * 114 - brightness = brightness - return brightness - - -def _black_or_white_by_color_brightness(color): - if _color_brightness(color) >= 500: - return "black" - else: - return "white" - - -def _normalized_channel(color): - # Implementation of contrast ratio and relative luminance method - if color <= 0.03928: - return color / 12.92 - else: - return ((color + 0.055) / 1.055) ** 2.4 - - -def _luminance(color): - rg = _normalized_channel(color[0]) - gg = _normalized_channel(color[1]) - bg = _normalized_channel(color[2]) - return 0.2126 * rg + 0.7152 * gg + 0.0722 * bg - - -def _black_or_white_by_contrast_ratio(color): - l_color = _luminance(color) - l_black = 0.0 - l_white = 1.0 - b_contrast = (l_color + 0.05) / (l_black + 0.05) - w_contrast = (l_white + 0.05) / (l_color + 0.05) - return "white" if w_contrast >= b_contrast else "black" - - -def get_contrast_text_color(color, use_color_brightness=True): - if use_color_brightness: - contrast_color = _black_or_white_by_color_brightness(color) - else: - contrast_color = _black_or_white_by_contrast_ratio(color) - if contrast_color == "white": - return 1, 1, 1, 1 - else: - return 0, 0, 0, 1 - - -if __name__ == "__main__": - from kivy.utils import get_color_from_hex - - from kivymd.color_definitions import colors, text_colors - - for c in colors.items(): - if c[0] in ["Light", "Dark"]: - continue - color = c[0] - print(f"For the {color} color palette:") - for name, hex_color in c[1].items(): - if hex_color: - col = get_color_from_hex(hex_color) - col_bri = get_contrast_text_color(col) - con_rat = get_contrast_text_color( - col, use_color_brightness=False - ) - text_color = text_colors[c[0]][name] - print( - f" The {name} hue gives {col_bri} using color " - f"brightness, {con_rat} using contrast ratio, and " - f"{text_color} from the MD spec" - ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__init__.py index d5270c6..2f0df60 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__init__.py @@ -1,11 +1,3 @@ __all__ = ("toast",) -from kivy.utils import platform - -if platform == "android": - try: - from .androidtoast import toast - except ModuleNotFoundError: - from .kivytoast import toast -else: - from .kivytoast import toast +from .androidtoast import toast diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/__init__.cpython-311.pyc index 740a92a..c7b08f6 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/androidtoast.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/androidtoast.cpython-311.pyc new file mode 100644 index 0000000..718f10e Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/__pycache__/androidtoast.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/androidtoast.py b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast.py similarity index 81% rename from kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/androidtoast.py rename to kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast.py index 18af63e..1ab9b4e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/androidtoast.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast.py @@ -8,26 +8,27 @@ AndroidToast # Will be automatically used native implementation of the toast # if your application is running on an Android device. - # Otherwise, will be used toast implementation - # from the kivymd/toast/kivytoast package. + # On desktop use `MDSnackbar < https://kivymd.readthedocs.io/en/latest/components/snackbar/>`_ from kivy.lang import Builder - from kivy.uix.screenmanager import ScreenManager from kivymd.toast import toast from kivymd.app import MDApp KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor - MDFlatButton: - text: "My Toast" + MDButton: pos_hint:{"center_x": .5, "center_y": .5} on_press: app.show_toast() + + MDButtonText: + text: "Make toast" ''' - class Test(MDApp): + class Example(MDApp): def build(self): return Builder.load_string(KV) @@ -35,7 +36,7 @@ AndroidToast toast("Hello World", True, 80, 200, 0) - Test().run() + Example().run() """ __all__ = ("toast",) @@ -47,10 +48,10 @@ if platform != "android": f"{platform.capitalize()} platform does not support Android Toast" ) +from android import mActivity from android.runnable import run_on_ui_thread from jnius import autoclass -activity = autoclass("org.kivy.android.PythonActivity").mActivity Toast = autoclass("android.widget.Toast") String = autoclass("java.lang.String") @@ -63,7 +64,7 @@ def toast(text, length_long=False, gravity=0, y=0, x=0): :param length_long: the amount of time (in seconds) that the toast is visible on the screen; :param text: text to be displayed in the toast; - :param short_duration: duration of the toast, if `True` the toast + :param length_long: duration of the toast, if `True` the toast will last 2.3s but if it is `False` the toast will last 3.9s; :param gravity: refers to the toast position, if it is 80 the toast will be shown below, if it is 40 the toast will be displayed above; @@ -76,6 +77,6 @@ def toast(text, length_long=False, gravity=0, y=0, x=0): """ duration = Toast.LENGTH_SHORT if length_long else Toast.LENGTH_LONG - t = Toast.makeText(activity, String(text), duration) + t = Toast.makeText(mActivity, String(text), duration) t.setGravity(gravity, x, y) t.show() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__init__.py deleted file mode 100644 index 300dd2d..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Toast for Android device -======================== - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toast.png - :align: center - -""" - -__all__ = ("toast",) - -from .androidtoast import toast diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 3746d72..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-311.pyc deleted file mode 100644 index 33b41e5..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/androidtoast/__pycache__/androidtoast.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__init__.py deleted file mode 100644 index 0d9c204..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__all__ = ("toast",) - -from .kivytoast import toast diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 7d53d1f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-311.pyc deleted file mode 100644 index 97d0b7f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/__pycache__/kivytoast.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/kivytoast.py b/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/kivytoast.py deleted file mode 100644 index c190666..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/toast/kivytoast/kivytoast.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -KivyToast -========= - -.. rubric:: Implementation of toasts for desktop. - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.toast import toast - - KV = ''' - MDScreen: - - MDTopAppBar: - title: 'Test Toast' - pos_hint: {'top': 1} - left_action_items: [['menu', lambda x: x]] - - MDRaisedButton: - text: 'TEST KIVY TOAST' - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_toast() - ''' - - - class Test(MDApp): - def show_toast(self): - '''Displays a toast on the screen.''' - - toast('Test Kivy Toast') - - def build(self): - return Builder.load_string(KV) - - Test().run() -""" - -from typing import List - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ListProperty, NumericProperty -from kivy.uix.label import Label - -from kivymd.uix.dialog import BaseDialog - -Builder.load_string( - """ -: - size_hint: (None, None) - pos_hint: {"center_x": 0.5, "center_y": 0.1} - opacity: 0 - auto_dismiss: True - overlay_color: [0, 0, 0, 0] - canvas: - Color: - rgba: root._md_bg_color - RoundedRectangle: - pos: self.pos - size: self.size - radius: root.radius -""" -) - - -class Toast(BaseDialog): - duration = NumericProperty(2.5) - """ - The amount of time (in seconds) that the toast is visible on the screen. - - :attr:`duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2.5`. - """ - - _md_bg_color = ListProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.label_toast = Label(size_hint=(None, None), markup=True, opacity=0) - self.label_toast.bind(texture_size=self.label_check_texture_size) - self.add_widget(self.label_toast) - - def label_check_texture_size( - self, instance_label: Label, texture_size: List[int] - ) -> None: - """ - Resizes the text if the text texture is larger than the screen size. - Sets the size of the toast according to the texture size of the toast - text. - """ - - texture_width, texture_height = texture_size - if texture_width > Window.width: - instance_label.text_size = (Window.width - dp(10), None) - instance_label.texture_update() - texture_width, texture_height = instance_label.texture_size - self.size = (texture_width + 25, texture_height + 25) - - def toast(self, text_toast: str) -> None: - """Displays a toast.""" - - self.label_toast.text = text_toast - self.open() - - def on_open(self) -> None: - """Default open event handler.""" - - self.fade_in() - Clock.schedule_once(self.fade_out, self.duration) - - def fade_in(self) -> None: - """Animation of opening toast on the screen.""" - - anim = Animation(opacity=1, duration=0.4) - anim.start(self.label_toast) - anim.start(self) - - def fade_out(self, *args) -> None: - """Animation of hiding toast on the screen.""" - - anim = Animation(opacity=0, duration=0.4) - anim.bind(on_complete=lambda *x: self.dismiss()) - anim.start(self.label_toast) - anim.start(self) - - def on_touch_down(self, touch): - if not self.collide_point(*touch.pos): - if self.auto_dismiss: - self.fade_out() - return False - super().on_touch_down(touch) - return True - - -def toast( - text: str = "", background: list = None, duration: float = 2.5 -) -> None: - """ - Displays a toast. - - :param text: text to be displayed in the toast; - :param duration: the amount of time (in seconds) that the toast is visible on the screen - :param background: toast background color in ``rgba`` format; - """ - - if background is None: - background = [0.2, 0.2, 0.2, 1] - Toast(duration=duration, _md_bg_color=background).toast(text) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/__init__.cpython-311.pyc index 8f6f999..307f084 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/argument_parser.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/argument_parser.cpython-311.pyc index 2fe6860..0e41f32 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/argument_parser.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/__pycache__/argument_parser.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/__init__.cpython-311.pyc index 2c962a9..23d20be 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/app.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/app.cpython-311.pyc index c812754..e1e64e4 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/app.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/hotreload/__pycache__/app.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/__pycache__/__init__.cpython-311.pyc index b81432f..39f4a0a 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-311.pyc index a107ec2..5d050c5 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-311.pyc index 883723b..b39ac46 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/packaging/pyinstaller/__pycache__/hook-kivymd.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/__init__.cpython-311.pyc index c4dcc33..44e1050 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_firebase.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_firebase.cpython-311.pyc index f99442c..326044c 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_firebase.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_firebase.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_restdb.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_restdb.cpython-311.pyc index 13cc4ce..50394df 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_restdb.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/Model/__pycache__/database_restdb.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/__pycache__/__init__.cpython-311.pyc index f691a8d..0868e5e 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/__init__.cpython-311.pyc index a81ee14..1e26d31 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/translation.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/translation.cpython-311.pyc index d406224..acebfd9 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/translation.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/MVC/libs/__pycache__/translation.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/__init__.cpython-311.pyc index b862aa9..8a15b8f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/add_view.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/add_view.cpython-311.pyc index 1201988..3baff4a 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/add_view.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/add_view.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/create_project.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/create_project.cpython-311.pyc index bc1de40..4abba75 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/create_project.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/tools/patterns/__pycache__/create_project.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__init__.py index 9aec2dc..bb70663 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__init__.py @@ -5,10 +5,8 @@ from kivy.uix.floatlayout import FloatLayout from kivy.uix.label import Label from kivy.uix.screenmanager import Screen -from kivymd.uix.behaviors import SpecificBackgroundColorBehavior - -class MDAdaptiveWidget(SpecificBackgroundColorBehavior): +class MDAdaptiveWidget: adaptive_height = BooleanProperty(False) """ If `True`, the following properties will be applied to the widget: diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/__init__.cpython-311.pyc index 1b629c9..f47480e 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/anchorlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/anchorlayout.cpython-311.pyc index 508b84d..5703abe 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/anchorlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/anchorlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/boxlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/boxlayout.cpython-311.pyc index f686ee0..ecae611 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/boxlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/boxlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/carousel.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/carousel.cpython-311.pyc deleted file mode 100644 index 470d066..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/carousel.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/circularlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/circularlayout.cpython-311.pyc index 10f3c90..8a36d48 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/circularlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/circularlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/floatlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/floatlayout.cpython-311.pyc index d596daf..4ca7bf7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/floatlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/floatlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/gridlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/gridlayout.cpython-311.pyc index c8bb1e8..8a2a2f7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/gridlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/gridlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/hero.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/hero.cpython-311.pyc index 8655652..40f45bb 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/hero.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/hero.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recyclegridlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recyclegridlayout.cpython-311.pyc index 5ee01ef..eb0b9c9 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recyclegridlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recyclegridlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recycleview.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recycleview.cpython-311.pyc index df20625..d1c8389 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recycleview.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/recycleview.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/relativelayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/relativelayout.cpython-311.pyc index 086d1b4..3919a38 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/relativelayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/relativelayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/responsivelayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/responsivelayout.cpython-311.pyc index 2abf31d..a35d893 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/responsivelayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/responsivelayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screen.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screen.cpython-311.pyc index 3240ec3..48cbb12 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screen.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screen.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screenmanager.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screenmanager.cpython-311.pyc index aa8f94f..4eff232 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screenmanager.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/screenmanager.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/scrollview.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/scrollview.cpython-311.pyc index 474845f..a3fb0c7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/scrollview.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/scrollview.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/stacklayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/stacklayout.cpython-311.pyc index f534fc7..b5bc060 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/stacklayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/stacklayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/taptargetview.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/taptargetview.cpython-311.pyc deleted file mode 100644 index 43c945f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/taptargetview.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/widget.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/widget.cpython-311.pyc index 0573ed8..4f92d4a 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/widget.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/__pycache__/widget.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/anchorlayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/anchorlayout.py index fe43542..381dc3a 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/anchorlayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/anchorlayout.py @@ -15,7 +15,7 @@ AnchorLayout AnchorLayout: canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -26,7 +26,7 @@ MDAnchorLayout .. code-block:: kv MDAnchorLayout: - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor """ __all__ = ("MDAnchorLayout",) @@ -35,13 +35,24 @@ from kivy.uix.anchorlayout import AnchorLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDAnchorLayout( - DeclarativeBehavior, ThemableBehavior, AnchorLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + AnchorLayout, + MDAdaptiveWidget, ): """ - Anchor layout class. For more information, see in the - :class:`~kivy.uix.anchorlayout.AnchorLayout` class documentation. + Anchor layout class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.anchorlayout.AnchorLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__init__.py new file mode 100644 index 0000000..b848375 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__init__.py @@ -0,0 +1,11 @@ +# NOQA F401 +from .appbar import ( + MDTopAppBar, + MDTopAppBarTitle, + MDBottomAppBar, + MDActionTopAppBarButton, + MDActionBottomAppBarButton, + MDFabBottomAppBarButton, + MDTopAppBarLeadingButtonContainer, + MDTopAppBarTrailingButtonContainer, +) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..eb32916 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__pycache__/appbar.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__pycache__/appbar.cpython-311.pyc new file mode 100644 index 0000000..e029a5d Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/__pycache__/appbar.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/appbar.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/appbar.kv new file mode 100644 index 0000000..fdd54de --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/appbar.kv @@ -0,0 +1,110 @@ + + size_hint_x: None + width: self.minimum_width + padding: "8dp", 0, "16dp", 0 + + + + size_hint_x: None + width: self.minimum_width + padding: "16dp", 0, "16dp", 0 + spacing: "4dp" + + + + pos_hint: {"center_y": .5} + color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_icon_color == "Primary" else \ + self.icon_color + + + + padding: + [ + self._appbar._left_padding if self._appbar else 0, + 0, + self._appbar._right_padding if self._appbar else 0, + 0, + ] + font_style: + { \ + "small": "Title", \ + "medium": "Headline", \ + "large": "Headline", \ + }[self._appbar.type if self._appbar else "small"] + role: + { \ + "small": "large", \ + "medium": "small", \ + "large": "medium", \ + }[self._appbar.type if self._appbar else "large"] + adaptive_width: + ( \ + True \ + if self._appbar.type == "small" else \ + False \ + ) \ + if self._appbar else True + size_hint_x: + ( \ + None \ + if self._appbar.type == "small" else \ + 1 \ + ) \ + if self._appbar else None + + + + canvas: + Color: + group: "md-top-app-bar-color" + rgba: + self.theme_cls.surfaceColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color + Rectangle: + pos: self.pos + size: self.size + + orientation: + "vertical" \ + if self.type in ("medium", "large") else \ + "horizontal" + size_hint_y: None + height: + { \ + "small": "64dp", \ + "medium": "112dp", \ + "large": "152dp", \ + }[self.type] + + BoxLayout: + id: root_box + + BoxLayout: + id: title_box + padding: "16dp", 0, "16dp", 0 + + + + elevation_level: 0 + theme_shadow_color: "Custom" + shadow_color: self.theme_cls.transparentColor + + + + size_hint_y: None + height: "80dp" + elevation_level: + 2 \ + if self.theme_elevation_level == "Primary" else \ + self.elevation_level + shadow_softness: + 2 \ + if self.theme_shadow_softness == "Primary" else \ + self.shadow_softness + md_bg_color: + self.theme_cls.surfaceContainerColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/appbar.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/appbar.py new file mode 100644 index 0000000..65a71a0 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/appbar/appbar.py @@ -0,0 +1,1272 @@ +""" +Components/Appbar +================= + +.. seealso:: + + `Material Design spec, App bars: top `_ + + `Material Design spec, App bars: bottom `_ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-top.png + :align: center + +`KivyMD` provides the following bar positions for use: + +- TopAppBar_ +- BottomAppBar_ + +.. TopAppBar_: + +TopAppBar +--------- + +- Contains a title and actions related to the current screen +- Four types: center-aligned, small, medium, and large +- On scroll, apply a container fill color to separate app bar from body content +- Top app bars have the same width as the device window + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-available-types.png + :align: center + +1. Center-aligned +2. Small +3. Medium +4. Large + +.. note:: KivyMD does not provide a `Center-aligned` type panel. But you can + easily create this pit panel yourself (read the documentation below). + +Usage +----- + +.. code-block:: kv + + MDTopAppBar: + type: "small" + + MDTopAppBarLeadingButtonContainer: + + MDActionTopAppBarButton: + icon: "menu" + + MDTopAppBarTitle: + text: "AppBar Center-aligned" + pos_hint: {"center_x": .5} + + MDTopAppBarTrailingButtonContainer: + + MDActionTopAppBarButton: + icon: "account-circle-outline" + +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-anatomy.png + :align: center + +Configurations +============== + +1. Center-aligned +----------------- + +.. code-block:: kv + + MDScreen: + md_bg_color: self.theme_cls.secondaryContainerColor + + MDTopAppBar: + type: "small" + size_hint_x: .8 + pos_hint: {"center_x": .5, "center_y": .5} + + MDTopAppBarLeadingButtonContainer: + + MDActionTopAppBarButton: + icon: "menu" + + MDTopAppBarTitle: + text: "AppBar small" + pos_hint: {"center_x": .5} + + MDTopAppBarTrailingButtonContainer: + + MDActionTopAppBarButton: + icon: "account-circle-outline" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-center-aligned.png + :align: center + +2. Small +-------- + +.. code-block:: kv + + MDScreen: + md_bg_color: self.theme_cls.secondaryContainerColor + + MDTopAppBar: + type: "small" + size_hint_x: .8 + pos_hint: {"center_x": .5, "center_y": .5} + + MDTopAppBarLeadingButtonContainer: + + MDActionTopAppBarButton: + icon: "arrow-left" + + MDTopAppBarTitle: + text: "AppBar small" + + MDTopAppBarTrailingButtonContainer: + + MDActionTopAppBarButton: + icon: "attachment" + + MDActionTopAppBarButton: + icon: "calendar" + + MDActionTopAppBarButton: + icon: "dots-vertical" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-small.png + :align: center + +3. Medium +--------- + +.. code-block:: kv + + MDTopAppBar: + type: "medium" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-medium.png + :align: center + +4. Large +-------- + +.. code-block:: kv + + MDTopAppBar: + type: "large" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/top-appbar-large.png + :align: center + +.. BottomAppBar: + +BottomAppBar +------------ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-bottom-m3.png + :align: center + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDBottomAppBar: + + MDFabBottomAppBarButton: + icon: "plus" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-1.png + :align: center + +Add action items +---------------- + +.. code-block:: kv + + #:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton + + + MDScreen: + + MDBottomAppBar: + action_items: + [ + MDActionBottomAppBarButton(icon="gmail"), + MDActionBottomAppBarButton(icon="label-outline"), + MDActionBottomAppBarButton(icon="bookmark"), + ] + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-2.png + :align: center + +Change action items +------------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.appbar import MDActionBottomAppBarButton + + KV = ''' + #:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton + + + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDBottomAppBar: + id: bottom_appbar + action_items: + [ + MDActionBottomAppBarButton(icon="gmail"), + MDActionBottomAppBarButton(icon="bookmark"), + ] + + MDFabBottomAppBarButton: + icon: "plus" + on_release: app.change_actions_items() + ''' + + + class Example(MDApp): + def change_actions_items(self): + self.root.ids.bottom_appbar.action_items = [ + MDActionBottomAppBarButton(icon="magnify"), + MDActionBottomAppBarButton(icon="trash-can-outline"), + MDActionBottomAppBarButton(icon="download-box-outline"), + ] + + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-3.gif + :align: center + +A practical example +------------------- + +.. code-block:: python + + import asynckivy + + from kivy.clock import Clock + from kivy.lang import Builder + from kivy.properties import StringProperty, BooleanProperty, ObjectProperty + from kivy.uix.behaviors import FocusBehavior + from kivy.uix.recycleboxlayout import RecycleBoxLayout + from kivy.uix.recycleview.layout import LayoutSelectionBehavior + from kivy.uix.recycleview.views import RecycleDataViewBehavior + + from kivymd.uix.appbar import MDActionBottomAppBarButton + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.app import MDApp + + from faker import Faker # pip install Faker + + KV = ''' + #:import MDFabBottomAppBarButton kivymd.uix.appbar.MDFabBottomAppBarButton + + + + orientation: "vertical" + adaptive_height: True + md_bg_color: "#373A22" if self.selected else "#1F1E15" + radius: 16 + padding: 0, 0, 0, "16dp" + + MDListItem: + theme_bg_color: "Custom" + md_bg_color: root.md_bg_color + radius: root.radius + ripple_effect: False + + MDListItemLeadingAvatar: + source: root.avatar + # radius: self.height / 2 + + MDListItemHeadlineText: + text: root.name + theme_text_color: "Custom" + text_color: "#8A8D79" + + MDListItemSupportingText: + text: root.time + theme_text_color: "Custom" + text_color: "#8A8D79" + + MDLabel: + text: root.text + adaptive_height: True + theme_text_color: "Custom" + text_color: "#8A8D79" + padding_x: "16dp" + shorten: True + shorten_from: "right" + + Widget: + + + MDFloatLayout: + md_bg_color: "#151511" + + RecycleView: + id: card_list + viewclass: "UserCard" + + SelectableRecycleGridLayout: + orientation: 'vertical' + spacing: "16dp" + padding: "16dp" + default_size: None, dp(120) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + multiselect: True + touch_multiselect: True + + MDBottomAppBar: + id: bottom_appbar + scroll_cls: card_list + allow_hidden: True + theme_bg_color: "Custom" + md_bg_color: "#232217" + + MDFabBottomAppBarButton: + id: fab_button + icon: "plus" + theme_bg_color: "Custom" + md_bg_color: "#373A22" + theme_icon_color: "Custom" + icon_color: "#ffffff" + ''' + + + class UserCard(RecycleDataViewBehavior, MDBoxLayout): + name = StringProperty() + time = StringProperty() + text = StringProperty() + avatar = StringProperty() + callback = ObjectProperty(lambda x: x) + + index = None + selected = BooleanProperty(False) + selectable = BooleanProperty(True) + + def refresh_view_attrs(self, rv, index, data): + self.index = index + return super().refresh_view_attrs(rv, index, data) + + def on_touch_down(self, touch): + if super().on_touch_down(touch): + return True + if self.collide_point(*touch.pos) and self.selectable: + Clock.schedule_once(self.callback) + return self.parent.select_with_touch(self.index, touch) + + def apply_selection(self, rv, index, is_selected): + self.selected = is_selected + rv.data[index]["selected"] = is_selected + + + class SelectableRecycleGridLayout( + FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout + ): + pass + + + class BottomAppBarButton(MDActionBottomAppBarButton): + theme_icon_color = "Custom" + icon_color = "#8A8D79" + + + class Example(MDApp): + selected_cards = False + + def build(self): + return Builder.load_string(KV) + + def on_tap_card(self, *args): + datas = [data["selected"] for data in self.root.ids.card_list.data] + if True in datas and not self.selected_cards: + self.root.ids.bottom_appbar.action_items = [ + BottomAppBarButton(icon="gmail"), + BottomAppBarButton(icon="label-outline"), + BottomAppBarButton(icon="bookmark"), + ] + self.root.ids.fab_button.icon = "pencil" + self.selected_cards = True + else: + if len(list(set(datas))) == 1 and not list(set(datas))[0]: + self.selected_cards = False + if not self.selected_cards: + self.root.ids.bottom_appbar.action_items = [ + BottomAppBarButton(icon="magnify"), + BottomAppBarButton(icon="trash-can-outline"), + BottomAppBarButton(icon="download-box-outline"), + ] + self.root.ids.fab_button.icon = "plus" + + def on_start(self): + async def generate_card(): + for i in range(10): + await asynckivy.sleep(0) + self.root.ids.card_list.data.append( + { + "name": fake.name(), + "time": fake.date(), + "avatar": fake.image_url(), + "text": fake.text(), + "selected": False, + "callback": self.on_tap_card, + } + ) + + self.on_tap_card() + fake = Faker() + Clock.schedule_once(lambda x: asynckivy.start(generate_card())) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-4.gif + :align: center + +API break +========= + +1.2.0 version +------------- + +.. code-block:: kv + + MDTopAppBar: + type_height: "large" + headline_text: "Headline" + left_action_items: [["arrow-left", lambda x: x]] + right_action_items: + [ \ + ["attachment", lambda x: x], \ + ["calendar", lambda x: x], \ + ["dots-vertical", lambda x: x], \ + ] + anchor_title: "left" + +2.0.0 version +------------- + +.. code-block:: kv + + MDTopAppBar: + type: "large" + + MDTopAppBarLeadingButtonContainer: + + MDActionTopAppBarButton: + icon: "arrow-left" + + MDTopAppBarTitle: + text: "AppBar small" + + MDTopAppBarTrailingButtonContainer: + + MDActionTopAppBarButton: + icon: "attachment" + + MDActionTopAppBarButton: + icon: "calendar" + + MDActionTopAppBarButton: + icon: "dots-vertical" +""" + +from __future__ import annotations + +__all__ = ( + "MDTopAppBar", + "MDTopAppBarTitle", + "MDBottomAppBar", + "MDActionTopAppBarButton", + "MDActionBottomAppBarButton", + "MDFabBottomAppBarButton", + "MDTopAppBarLeadingButtonContainer", + "MDTopAppBarTrailingButtonContainer", +) + +import os + +from kivy import Logger +from kivy.animation import Animation +from kivy.clock import Clock +from kivy.core.window import Window +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import ( + BooleanProperty, + ListProperty, + NumericProperty, + ObjectProperty, + OptionProperty, + StringProperty, + ColorProperty, +) +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.scrollview import ScrollView +from kivy.uix.widget import Widget + +from kivymd import uix_path +from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import ( + CommonElevationBehavior, + DeclarativeBehavior, + RotateBehavior, + ScaleBehavior, + BackgroundColorBehavior, +) +from kivymd.uix.button import MDFabButton, MDIconButton +from kivymd.uix.controllers import WindowController +from kivymd.uix.label import MDLabel +from kivymd.utils.set_bars_colors import set_bars_colors + +import asynckivy + +with open( + os.path.join(uix_path, "appbar", "appbar.kv"), encoding="utf-8" +) as kv_file: + Builder.load_string(kv_file.read()) + + +class BaseTopAppBarButtonContainer(DeclarativeBehavior, BoxLayout): + # kivymd.uix.appbar.appbar.MDTopAppBar object. + _appbar = ObjectProperty() + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDActionTopAppBarButton): + Clock.schedule_once(lambda x: self._check_icon_color(widget)) + + return super().add_widget(widget) + + def _check_icon_color(self, widget): + if widget.theme_icon_color == "Primary" and widget.icon_color == None: + widget.theme_icon_color = "Custom" + widget.icon_color = widget.theme_cls.onSurfaceColor + + +class MDFabBottomAppBarButton(MDFabButton, RotateBehavior, ScaleBehavior): + """ + Implements a floating action button (FAB) for a bar with type 'bottom'. + + For more information, see in the + :class:`~kivymd.uix.button.button.MDFabButton` and + :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + classes documentation. + """ + + +class MDActionTopAppBarButton(MDIconButton): + """ + Implements action buttons on the bar. + + For more information, see in the + :class:`~kivymd.uix.button.button.MDIconButton` class documentation. + """ + + md_bg_color_disabled = ColorProperty(None) + """ + The background color in (r, g, b, a) or string format of the button when + the button is disabled. + + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + +class MDActionBottomAppBarButton(MDActionTopAppBarButton): + """ + Implements action buttons for a + :class:'~kivymd.uix.appbar.appbar.MDBottomAppBar' class. + + .. versionadded:: 1.2.0 + + For more information, see in the + :class:`~kivymd.uix.appbar.appbar.MDActionTopAppBarButton` + class documentation. + """ + + +class MDTopAppBarTitle(MDLabel): + """ + Implements the panel title. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + _appbar = ObjectProperty() + _title_width = NumericProperty(0) + + # FIXME: When changing the text, the texture_size property returns an + # incorrect value, which causes the panel title to shift. + def on_text(self, instance, value) -> None: + """Fired when the :attr:`text` value changes.""" + + def set_title_width(*args) -> None: + self._title_width = self.texture_size[0] + + Clock.schedule_once(set_title_width) + + def on_pos_hint(self, instance, value) -> None: + """Fired when the :attr:`pos_hint` value changes.""" + + if self._appbar: + Clock.schedule_once( + lambda x: self._appbar._set_padding_title(value) + ) + + +class MDTopAppBarLeadingButtonContainer(BaseTopAppBarButtonContainer): + """ + Implements a container for the leading action buttons. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + """ + + +class MDTopAppBarTrailingButtonContainer(BaseTopAppBarButtonContainer): + """ + Implements a container for the trailing action buttons. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + """ + + +# FIXME: The on_enter/on_leave event is not triggered for +# MDActionTopAppBarButton buttons in the MDTopAppBarTrailingButtonContainer +# container. +# When the screen size is changed on desktop devices, the position of the +# trailing container is shifted until the screen size change is completed. +class MDTopAppBar( + DeclarativeBehavior, + ThemableBehavior, + CommonElevationBehavior, + BackgroundColorBehavior, + BoxLayout, + WindowController, +): + """ + Top app bar class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` and + :class:`~kivymd.uix.controllers.windowcontroller.WindowController` + classes documentation. + + :Events: + `on_action_button` + Method for the button used for the :class:`~MDBottomAppBar` class. + """ + + set_bars_color = BooleanProperty(False) + """ + If `True` the background color of the bar status will be set automatically + according to the current color of the bar. + + .. versionadded:: 1.0.0 + + See `set_bars_colors `_ + for more information. + + :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + type = OptionProperty("small", options=["medium", "large", "small"]) + """ + Bar height type. + + .. versionadded:: 1.0.0 + + Available options are: 'medium', 'large', 'small'. + + :attr:`type_height` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'small'`. + """ + + _trailing_button_container = ObjectProperty() + _leading_button_container = ObjectProperty() + _appbar_title = ObjectProperty() + _right_padding = NumericProperty(0) + _left_padding = NumericProperty(0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + Clock.schedule_once(lambda x: self.on_size(self, (0, 0))) + + def on_type(self, instance, value) -> None: + def on_type(*args): + if value in ("medium", "large"): + self.ids.root_box.add_widget(Widget(), index=1) + + Clock.schedule_once(on_type, 0.5) + + def on_size(self, instance, size) -> None: + """Fired when the application screen size changes.""" + + if self._appbar_title: + if not self._appbar_title._title_width: + self._appbar_title._title_width = ( + self._appbar_title.texture_size[0] + ) + self._right_padding = 0 + self._left_padding = 0 + self._appbar_title.on_pos_hint( + self._appbar_title, self._appbar_title.pos_hint + ) + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDTopAppBarTitle): + widget._appbar = self + self._appbar_title = widget + widget.bind(text=lambda *x: self.on_size(*x)) + Clock.schedule_once(lambda x: self._add_title(widget)) + elif isinstance(widget, MDTopAppBarTrailingButtonContainer): + self._trailing_button_container = widget + widget._appbar = self + Clock.schedule_once(lambda x: self.ids.root_box.add_widget(widget)) + elif isinstance(widget, MDTopAppBarLeadingButtonContainer): + widget._appbar = self + self._leading_button_container = widget + Clock.schedule_once(lambda x: self.ids.root_box.add_widget(widget)) + else: + return super().add_widget(widget) + + def _add_title(self, widget): + if self.type == "small": + self.ids.root_box.add_widget(widget) + else: + self.ids.title_box.add_widget(widget) + + def _set_padding_title(self, value): + if value.get("center_x", 0) == 0.5 and self.type == "small": + if ( + not self._trailing_button_container + and self._leading_button_container + ): + self._left_padding = ( + (self.width // 2) + - ( + self._leading_button_container.width + + (self._appbar_title._title_width // 2) + ) + ) - self._left_padding + elif ( + self._trailing_button_container + and not self._leading_button_container + ): + self._left_padding = ( + (self.width // 2) - (self._appbar_title._title_width // 2) + ) - self._left_padding + self._right_padding = ( + (self.width // 2) + - ( + self._trailing_button_container.width + + (self._appbar_title._title_width // 2) + ) + ) - self._right_padding + elif ( + not self._trailing_button_container + and not self._leading_button_container + ): + self._left_padding = ( + (self.width // 2) - (self._appbar_title._title_width // 2) + ) - self._left_padding + self._right_padding = ( + (self.width // 2) - (self._appbar_title._title_width // 2) + ) - self._right_padding + elif ( + self._trailing_button_container + and self._leading_button_container + ): + self._left_padding = ( + (self.width // 2) + - ( + self._leading_button_container.width + + (self._appbar_title._title_width // 2) + ) + ) - self._left_padding + self._right_padding = ( + (self.width // 2) + - ( + self._trailing_button_container.width + + (self._appbar_title._title_width // 2) + ) + ) - self._right_padding + elif ( + not value + and self._trailing_button_container + and self._leading_button_container + ): + if self.type == "small": + + self._right_padding = ( + self.width + - ( + self._trailing_button_container.width + + self._leading_button_container.width + + self._appbar_title._title_width + ) + - self._right_padding + ) + elif ( + not value + and self._trailing_button_container + and not self._leading_button_container + ): + if self.type == "small": + self._right_padding = ( + self.width + - ( + self._trailing_button_container.width + + self._appbar_title._title_width + ) + - self._right_padding + ) + self._left_padding = dp(16) + elif ( + not value + and not self._trailing_button_container + and not self._leading_button_container + ): + self._left_padding = dp(16) + + +class MDBottomAppBar( + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + CommonElevationBehavior, + FloatLayout, +): + """ + Bottom app bar class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivy.uix.floatlayout.FloatLayout` + classes documentation. + + :Events: + `on_show_bar` + The method is fired when the :class:`~MDBottomAppBar` panel + is shown. + `on_hide_bar` + The method is fired when the :class:`~MDBottomAppBar` panel + is hidden. + """ + + action_items = ListProperty() + """ + The icons on the left bar. + + .. versionadded:: 1.2.0 + + :attr:`action_items` is an :class:`~kivy.properties.ListProperty` + and defaults to `[]`. + """ + + animation = BooleanProperty(True) + """ + # TODO: add description. + # FIXME: changing the value does not affect anything. + + .. versionadded:: 1.2.0 + + :attr:`animation` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + + show_transition = StringProperty("linear") + """ + Type of button display transition. + + .. versionadded:: 1.2.0 + + :attr:`show_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'linear'`. + """ + + hide_transition = StringProperty("in_back") + """ + Type of button hidden transition. + + .. versionadded:: 1.2.0 + + :attr:`hide_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'in_back'`. + """ + + hide_duration = NumericProperty(0.4) + """ + Duration of button hidden transition. + + .. versionadded:: 1.2.0 + + :attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ + + show_duration = NumericProperty(0.2) + """ + Duration of button display transition. + + .. versionadded:: 1.2.0 + + :attr:`show_duration` is a :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ + + scroll_cls = ObjectProperty() + """ + Widget inherited from the :class:`~kivy.uix.scrollview.ScrollView` class. + The value must be set if the :attr:`allow_hidden` parameter is `True`. + + .. versionadded:: 1.2.0 + + :attr:`scroll_cls` is a :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + allow_hidden = BooleanProperty(False) + """ + Allows or disables hiding the panel when scrolling content. + If the value is `True`, the :attr:`scroll_cls` parameter must be specified. + + .. versionadded:: 1.2.0 + + :attr:`allow_hidden` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + bar_is_hidden = BooleanProperty(False) + """ + Is the panel currently hidden. + + .. versionadded:: 1.2.0 + + :attr:`bar_is_hidden` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + _padding = dp(16) + _x = -dp(48) + _scroll_cls_y = 0 + _cache = [] + _current_data = [] + _wait_removed = False + _animated_hidden = True + _animated_show = True + _fab_bottom_app_bar_button = None + _action_overflow_button = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_event_type("on_show_bar") + self.register_event_type("on_hide_bar") + + def button_centering_animation( + self, + button: MDActionBottomAppBarButton | MDFabBottomAppBarButton, + ) -> None: + """ + Animation of centering buttons for + :class:`~MDActionOverFlowButton`, + :class:`~MDActionBottomAppBarButton` and + :class:`~MDFabBottomAppBarButton` classes. + """ + + if self.animation: + Animation( + y=self.height / 2 - dp(48) / 2, + opacity=1, + d=self.show_duration, + t=self.show_transition, + ).start(button) + + def check_scroll_direction(self, scroll_cls, y: float) -> None: + """ + Checks the scrolling direction. + Depending on the scrolling direction, hides or shows the + :class:`~MDBottomAppBar` panel. + """ + + if round(y, 1) < self._scroll_cls_y and not self.bar_is_hidden: + self.hide_bar() + if round(y, 1) > self._scroll_cls_y and self.bar_is_hidden: + self.show_bar() + + self._scroll_cls_y = round(y, 1) + + def show_bar(self) -> None: + """Show :class:`~MDBottomAppBar` panel.""" + + def on_complete(*args): + self.dispatch("on_show_bar") + + def on_progress(animation, instance, progress): + if progress > 0.5 and self._animated_show: + self._animated_show = False + for i, widget in enumerate(self.children): + if isinstance(widget, MDActionBottomAppBarButton): + anim_icon = Animation( + y=self.height / 2 - dp(48) / 2, + d=self.show_duration, + t=self.show_transition, + ) + Clock.schedule_once( + lambda x, y=widget: anim_icon.start(y), + i / 10, + ) + if self._fab_bottom_app_bar_button: + Animation( + y=self._fab_bottom_app_bar_button.y + dp(4), + d=self.show_duration, + t=self.show_transition, + ).start(self._fab_bottom_app_bar_button) + + self.bar_is_hidden = False + self._animated_show = True + anim = Animation( + y=0, + d=self.show_duration, + t=self.show_transition, + ) + anim.bind(on_progress=on_progress, on_complete=on_complete) + anim.start(self) + + def hide_bar(self) -> None: + """Hide :class:`~MDBottomAppBar` panel.""" + + def on_complete(*args): + self.dispatch("on_hide_bar") + + def on_progress(animation, instance, progress): + if ( + progress > 0.5 + and self._animated_hidden + and widget_icon == instance.icon + ): + self._animated_hidden = False + anim_bar = Animation( + y=-self.height, + d=self.hide_duration, + # t=self.hide_transition, + ) + anim_bar.bind(on_complete=on_complete) + anim_bar.start(self) + + if self._fab_bottom_app_bar_button: + Animation( + y=self._fab_bottom_app_bar_button.y - dp(4), + d=self.hide_duration, + t=self.hide_transition, + ).start(self._fab_bottom_app_bar_button) + + self.bar_is_hidden = True + self._animated_hidden = True + len_children = len(self.children) + widget_icon = "" + + for i, widget in enumerate(self.children): + if isinstance(widget, MDActionBottomAppBarButton): + anim = Animation( + y=-widget.height, + d=self.hide_duration, + t=self.hide_transition, + ) + if i + 2 == len_children: + widget_icon = widget.icon + anim.bind(on_progress=on_progress) + Clock.schedule_once( + lambda x, y=widget: anim.start(y), + i / 10, + ) + + def on_show_bar(self, *args) -> None: + """ + The method is fired when the :class:`~MDBottomAppBar` panel + is shown. + """ + + def on_hide_bar(self, *args) -> None: + """ + The method is fired when the :class:`~MDBottomAppBar` panel + is hidden. + """ + + def on_scroll_cls(self, instance, scroll_cls) -> None: + """ + Fired when the value of the :attr:`scroll_cls` attribute changes. + """ + + def on_scroll_cls(*args): + if not self.allow_hidden: + Logger.warning( + "KivyMD: " + "In order for the bottom bar to be automatically hidden " + "in addition to the `scroll_cls` parameter, set the value " + "of the `allow_hidden` parameter to `True`" + ) + + if issubclass(scroll_cls.__class__, ScrollView): + if self.allow_hidden: + scroll_cls.bind(scroll_y=self.check_scroll_direction) + else: + raise TypeError( + f"The `scroll_cls` parameter must be an object inherited " + f"from the {ScrollView} class" + ) + + Clock.schedule_once(on_scroll_cls) + + def on_size(self, *args) -> None: + """Fired when the root screen is resized.""" + + if self._fab_bottom_app_bar_button: + self._fab_bottom_app_bar_button.x = Window.width - (dp(56) + dp(16)) + + def on_action_items(self, instance, value: list) -> None: + """ + Fired when the value of the :attr:`action_items` attribute changes. + """ + + def wait_removed(*args): + if len(self.children) == 1 or not self.children: + Clock.unschedule(wait_removed) + self._wait_removed = False + self._x = -dp(48) + asynckivy.start(add_widget()) + + async def add_widget(): + for button in value: + await asynckivy.sleep(0) + self.add_widget(button) + + if self._cache: + self._cache.append(value) + + for data in self._cache: + if value[0] in data: + for i, widget in enumerate(self.children): + if not self._wait_removed: + Clock.schedule_interval(wait_removed, 0) + self._wait_removed = True + if isinstance(widget, MDActionBottomAppBarButton): + anim = Animation( + y=-widget.height, + d=self.hide_duration, + t=self.hide_transition, + ) + anim.bind( + on_complete=lambda x, y=widget: self.remove_widget( + y + ) + ) + Clock.schedule_once( + lambda x, y=widget: anim.start(y), + i / 10, + ) + else: + self._cache.append(value) + self._current_data = value + asynckivy.start(add_widget()) + + def set_fab_opacity(self, *ars) -> None: + """ + Sets the transparency value of the:class:`~MDFabBottomAppBarButton` + button. + """ + + # self._fab_bottom_app_bar_button.opacity = 1 + + def set_fab_icon(self, instance, value) -> None: + """ + Animates the size of the :class:`~MDFabBottomAppBarButton` button. + """ + + # self._fab_bottom_app_bar_button.opacity = 0 + anim = Animation( + scale_value_x=0, + scale_value_y=0, + opacity=0, + d=self.hide_duration, + t=self.hide_transition, + ) + Animation( + scale_value_x=1, + scale_value_y=1, + opacity=1, + d=self.show_duration, + t=self.show_transition, + ) + anim.bind(on_complete=self.set_fab_opacity) + anim.start(instance) + + def add_widget(self, widget, index=0, canvas=None): + if isinstance(widget, MDActionBottomAppBarButton): + self._x += widget.width + widget.pos = ( + self._x + self._padding, + -dp(48) if self.animation else self.height / 2 - dp(48) / 2, + ) + widget.opacity = int(not self.animation) + super().add_widget(widget) + self.button_centering_animation(widget) + elif isinstance(widget, MDFabBottomAppBarButton): + widget.bind(icon=self.set_fab_icon) + self._fab_bottom_app_bar_button = widget + Clock.schedule_once(self.set_fab_opacity) + widget.scale_value_x = int(not self.animation) + widget.scale_value_y = int(not self.animation) + widget.pos = ( + Window.width - (dp(56) + self._padding), + self.height / 2 - dp(56) / 2, + ) + super().add_widget(widget) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__init__.py deleted file mode 100644 index 7a5e6cf..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .backdrop import MDBackdrop # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 0370b7a..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__pycache__/backdrop.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__pycache__/backdrop.cpython-311.pyc deleted file mode 100644 index 727f12e..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/__pycache__/backdrop.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/backdrop.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/backdrop.kv deleted file mode 100644 index 2f56d5b..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/backdrop.kv +++ /dev/null @@ -1,50 +0,0 @@ - - md_bg_color: - root.theme_cls.primary_color \ - if not root.back_layer_color \ - else root.back_layer_color - - MDBackdropToolbar: - id: toolbar - type_height: "small" - anchor_title: root.anchor_title - title: root.title - elevation: 0 - left_action_items: root.left_action_items - right_action_items: root.right_action_items - pos_hint: {"top": 1} - md_bg_color: - root.theme_cls.primary_color \ - if not root.back_layer_color \ - else root.back_layer_color - - _BackLayer: - id: back_layer - y: -toolbar.height - padding: 0, 0, 0, toolbar.height + dp(10) - - _FrontLayer: - id: _front_layer - md_bg_color: 0, 0, 0, 0 - orientation: "vertical" - size_hint_y: None - height: root.height - toolbar.height - padding: root.padding - md_bg_color: - root.theme_cls.bg_normal \ - if not root.front_layer_color \ - else root.front_layer_color - radius: - [root.radius_left, root.radius_right, - 0, 0] - - OneLineListItem: - id: header_button - text: root.header_text - divider: None - _no_ripple_effect: True - on_press: root.open() - - MDBoxLayout: - id: front_layer - padding: 0, 0, 0, "10dp" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/backdrop.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/backdrop.py deleted file mode 100644 index 276eb1c..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/backdrop/backdrop.py +++ /dev/null @@ -1,548 +0,0 @@ -""" -Components/Backdrop -=================== - -.. seealso:: - - `Material Design spec, Backdrop `_ - -.. rubric:: Skeleton layout for using :class:`~MDBackdrop`: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.png - :align: center - -Usage ------ - -.. code-block:: kv - - - - MDBackdrop: - - MDBackdropBackLayer: - - ContentForBackdropBackLayer: - - MDBackdropFrontLayer: - - ContentForBackdropFrontLayer: - -Example -------- - -.. tabs:: - - .. tab:: Declarative KV styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp - - # Your layouts. - Builder.load_string( - ''' - #:import os os - #:import Window kivy.core.window.Window - #:import IconLeftWidget kivymd.uix.list.IconLeftWidget - #:import images_path kivymd.images_path - - - - icon: "android" - - IconLeftWidget: - icon: root.icon - - - - backdrop: None - text: "Lower the front layer" - secondary_text: " by 50 %" - icon: "transfer-down" - on_press: root.backdrop.open(-Window.height / 2) - pos_hint: {"top": 1} - _no_ripple_effect: True - - - - size_hint: .8, .8 - source: os.path.join(images_path, "logo", "kivymd-icon-512.png") - pos_hint: {"center_x": .5, "center_y": .6} - ''' - ) - - # Usage example of MDBackdrop. - Builder.load_string( - ''' - - - MDBackdrop: - id: backdrop - left_action_items: [['menu', lambda x: self.open()]] - title: "Example Backdrop" - radius_left: "25dp" - radius_right: "0dp" - header_text: "Menu:" - - MDBackdropBackLayer: - MyBackdropBackLayer: - id: backlayer - - MDBackdropFrontLayer: - MyBackdropFrontLayer: - backdrop: backdrop - ''' - ) - - - class ExampleBackdrop(MDScreen): - pass - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ExampleBackdrop() - - - Example().run() - - .. tab:: Declarative python styles - - .. code-block:: python - - import os - - from kivy.core.window import Window - from kivy.uix.image import Image - - from kivymd import images_path - from kivymd.uix.backdrop import MDBackdrop - from kivymd.uix.backdrop.backdrop import ( - MDBackdropBackLayer, MDBackdropFrontLayer - ) - from kivymd.uix.list import TwoLineAvatarListItem, IconLeftWidget - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - return ( - MDScreen( - MDBackdrop( - MDBackdropBackLayer( - Image( - size_hint=(0.8, 0.8), - source=os.path.join(images_path, "logo", "kivymd-icon-512.png"), - pos_hint={"center_x": 0.5, "center_y": 0.6}, - ) - ), - MDBackdropFrontLayer( - TwoLineAvatarListItem( - IconLeftWidget(icon="transfer-down"), - text="Lower the front layer", - secondary_text=" by 50 %", - on_press=self.backdrop_open_by_50_percent, - pos_hint={"top": 1}, - _no_ripple_effect=True, - ), - ), - id="backdrop", - title="Example Backdrop", - radius_left="25dp", - radius_right="0dp", - header_text="Menu:", - ) - ) - ) - - def backdrop_open_by_50_percent(self, *args): - self.root.ids.backdrop.open(-Window.height / 2) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop.gif - :align: center - -.. Note:: `See full example `_ -""" - -__all__ = ( - "MDBackdropToolbar", - "MDBackdropFrontLayer", - "MDBackdropBackLayer", - "MDBackdrop", -) - -import os -from typing import Union - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.boxlayout import BoxLayout - -from kivymd import uix_path -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.card import MDCard -from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.toolbar.toolbar import ActionTopAppBarButton, MDTopAppBar - -with open( - os.path.join(uix_path, "backdrop", "backdrop.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDBackdrop(MDFloatLayout): - """ - For more information, see in the - :class:`~kivymd.uix.floatlayout.MDFloatLayout` class documentation. - - :Events: - :attr:`on_open` - When the front layer drops. - :attr:`on_close` - When the front layer rises. - """ - - anchor_title = OptionProperty("left", options=["left", "center", "right"]) - """ - Position toolbar title. Only used with `material_style = 'M3'` - Available options are: `'left'`, `'center'`, `'right'`. - - .. versionadded:: 1.0.0 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-anchor-title.png - :align: center - - :attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'left'`. - """ - - padding = ListProperty([0, 0, 0, 0]) - """ - Padding for contents of the front layer. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-padding.png - :align: center - - :attr:`padding` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - left_action_items = ListProperty() - """ - The icons and methods left of the :class:`kivymd.uix.toolbar.MDTopAppBar` - in back layer. For more information, see the - :class:`kivymd.uix.toolbar.MDTopAppBar` module - and :attr:`left_action_items` parameter. - - :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - right_action_items = ListProperty() - """ - Works the same way as :attr:`left_action_items`. - - :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - title = StringProperty() - """ - See the :class:`kivymd.uix.toolbar.MDTopAppBar.title` parameter. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - back_layer_color = ColorProperty(None) - """ - Background color of back layer in (r, g, b, a) or string format. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-back-layer-color.png - :align: center - - :attr:`back_layer_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - front_layer_color = ColorProperty(None) - """ - Background color of front layer in (r, g, b, a) or string format. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-front-layer-color.png - :align: center - - :attr:`front_layer_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - radius_left = NumericProperty("16dp") - """ - The value of the rounding radius of the upper left corner - of the front layer. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-radius-left.png - :align: center - - :attr:`radius_left` is an :class:`~kivy.properties.NumericProperty` - and defaults to `16dp`. - """ - - radius_right = NumericProperty("16dp") - """ - The value of the rounding radius of the upper right corner - of the front layer. - - :attr:`radius_right` is an :class:`~kivy.properties.NumericProperty` - and defaults to `16dp`. - """ - - header = BooleanProperty(True) - """ - Whether to use a header above the contents of the front layer. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header.png - :align: center - - :attr:`header` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - header_text = StringProperty("Header") - """ - Text of header. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-header-text.png - :align: center - - :attr:`header_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Header'`. - """ - - close_icon = StringProperty("close") - """ - The name of the icon that will be installed on the toolbar - on the left when opening the front layer. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/backdrop-close-icon.png - :align: center - - :attr:`close_icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'close'`. - """ - - opening_time = NumericProperty(0.2) - """ - The time taken for the panel to slide to the :attr:`state` `'open'`. - - .. versionadded:: 1.0.0 - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - opening_transition = StringProperty("out_quad") - """ - The name of the animation transition type to use when animating to - the :attr:`state` `'open'`. - - .. versionadded:: 1.0.0 - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_quad'`. - """ - - closing_time = NumericProperty(0.2) - """ - The time taken for the panel to slide to the :attr:`state` `'close'`. - - .. versionadded:: 1.0.0 - - :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_transition = StringProperty("out_quad") - """ - The name of the animation transition type to use when animating to - the :attr:`state` 'close'. - - .. versionadded:: 1.0.0 - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_quad'`. - """ - - _open_icon = "" - _front_layer_open = False - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_event_type("on_open") - self.register_event_type("on_close") - Clock.schedule_once( - lambda x: self.on_left_action_items(self, self.left_action_items) - ) - - def on_open(self) -> None: - """When the front layer drops.""" - - def on_close(self) -> None: - """When the front layer rises.""" - - def on_left_action_items(self, instance_backdrop, menu: list) -> None: - if menu: - self.left_action_items = [menu[0]] - else: - self.left_action_items = [["menu", lambda x: self.open()]] - self._open_icon = self.left_action_items[0][0] - - def on_header(self, instance_backdrop, value: bool) -> None: - def on_header(*args): - if not value: - self.ids._front_layer.remove_widget(self.ids.header_button) - - Clock.schedule_once(on_header) - - def open(self, open_up_to: int = 0) -> None: - """ - Opens the front layer. - - :open_up_to: - the height to which the front screen will be lowered; - if equal to zero - falls to the bottom of the screen; - """ - - self.animate_opacity_icon() - if self._front_layer_open: - self.close() - return - - if open_up_to: - if open_up_to < ( - self.ids.header_button.height - self.ids._front_layer.height - ): - y = self.ids.header_button.height - self.ids._front_layer.height - elif open_up_to > 0: - y = 0 - else: - y = open_up_to - else: - y = self.ids.header_button.height - self.ids._front_layer.height - - Animation(y=y, d=self.opening_time, t=self.opening_transition).start( - self.ids._front_layer - ) - self._front_layer_open = True - self.dispatch("on_open") - - def close(self) -> None: - """Opens the front layer.""" - - Animation(y=0, d=self.closing_time, t=self.closing_transition).start( - self.ids._front_layer - ) - self._front_layer_open = False - self.dispatch("on_close") - - def animate_opacity_icon( - self, - instance_icon_menu: Union[ActionTopAppBarButton, None] = None, - opacity_value: int = 0, - call_set_new_icon: bool = True, - ) -> None: - """Starts the opacity animation of the icon.""" - - if not instance_icon_menu: - instance_icon_menu = self.ids.toolbar.ids.left_actions.children[0] - anim = Animation( - opacity=opacity_value, - d=self.opening_time, - t=self.opening_transition, - ) - if call_set_new_icon: - anim.bind(on_complete=self.set_new_icon) - anim.start(instance_icon_menu) - - def set_new_icon( - self, - instance_animation: Animation, - instance_icon_menu: ActionTopAppBarButton, - ) -> None: - """ - Sets the icon of the button depending on the state of the backdrop. - """ - - instance_icon_menu.icon = ( - self.close_icon - if instance_icon_menu.icon == self._open_icon - else self._open_icon - ) - self.animate_opacity_icon(instance_icon_menu, 1, False) - - def add_widget(self, widget, index=0, canvas=None): - if widget.__class__ in (MDBackdropToolbar, _BackLayer, _FrontLayer): - return super().add_widget(widget) - else: - if widget.__class__ is MDBackdropBackLayer: - self.ids.back_layer.add_widget(widget) - elif widget.__class__ is MDBackdropFrontLayer: - self.ids.front_layer.add_widget(widget) - - -class MDBackdropToolbar(MDTopAppBar): - """ - Implements a toolbar for back content. - - For more information, see in the - :class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar` classes documentation. - """ - - -class MDBackdropFrontLayer(MDBoxLayout): - """ - Container for front content. - - For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` classes documentation. - """ - - -class MDBackdropBackLayer(MDBoxLayout): - """ - Container for back content. - - For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. - """ - - -class _BackLayer(BoxLayout): - pass - - -class _FrontLayer(MDCard): - pass diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__init__.py new file mode 100644 index 0000000..043d8e1 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__init__.py @@ -0,0 +1 @@ +from .badge import MDBadge # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..be3b64f Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__pycache__/badge.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__pycache__/badge.cpython-311.pyc new file mode 100644 index 0000000..a7db700 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/__pycache__/badge.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/badge.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/badge.kv new file mode 100644 index 0000000..4ab649e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/badge.kv @@ -0,0 +1,19 @@ + + font_style: "Label" + role: "small" + radius: [self.texture_size[1] / 2, ] + pos_hint: {"center_x": 0.5, "center_y": 0.5} + padding: "4dp", "2dp" + halign: "center" + valign: "center" + adaptive_size: True + md_bg_color: self.theme_cls.errorColor + text_color: self.theme_cls.onErrorColor + size_hint: None, None + size: self.texture_size + pos: + ( \ + self.parent.x + (self.parent.width / 2), \ + self.parent.y + (self.parent.height / 2) \ + ) \ + if self.parent else (0, 0) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/badge.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/badge.py new file mode 100644 index 0000000..8134046 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/badge/badge.py @@ -0,0 +1,74 @@ +""" +Components/Badge +================ + +.. versionadded:: 2.0.0 + + +.. seealso:: + + `Material Design 3 spec, Badge `_ + +.. rubric:: Badges show notifications, counts, or status information on + navigation items and icons. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/badges.png + :align: center + +Example +------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDIcon: + icon: "gmail" + pos_hint: {'center_x': .5, 'center_y': .5} + + MDBadge: + text: "12" + ''' + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/badges-example.png + :align: center +""" + +__all__ = ("MDBadge",) + +import os + +from kivy.lang import Builder + +from kivymd.uix.label import MDLabel +from kivymd import uix_path + +with open( + os.path.join(uix_path, "badge", "badge.kv"), encoding="utf-8" +) as kv_file: + Builder.load_string(kv_file.read()) + + +class MDBadge(MDLabel): + """ + Badge class. + + .. versionadded:: 2.0.0 + + For more information see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__init__.py deleted file mode 100644 index 3ed33d0..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .banner import MDBanner # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index adcaa4f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__pycache__/banner.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__pycache__/banner.cpython-311.pyc deleted file mode 100644 index 18e7813..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/__pycache__/banner.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/banner.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/banner.kv deleted file mode 100644 index 3d874ce..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/banner.kv +++ /dev/null @@ -1,85 +0,0 @@ -#:import Window kivy.core.window.Window - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - tertiary_text: root.text_message[2] - divider: None - _no_ripple_effect: True - - ImageLeftWidget: - source: root.icon - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - divider: None - _no_ripple_effect: True - - ImageLeftWidget: - source: root.icon - - - - text: root.text_message[0] - divider: None - _no_ripple_effect: True - - ImageLeftWidget: - source: root.icon - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - tertiary_text: root.text_message[2] - divider: None - _no_ripple_effect: True - - - - text: root.text_message[0] - secondary_text: root.text_message[1] - divider: None - _no_ripple_effect: True - - - - text: root.text_message[0] - divider: None - _no_ripple_effect: True - - - - size_hint_y: None - height: self.minimum_height - banner_y: 0 - orientation: "vertical" - y: Window.height - self.banner_y - - canvas: - Color: - rgba: 0, 0, 0, 0 - Rectangle: - pos: self.pos - size: self.size - - MDBoxLayout: - id: container_message - adaptive_height: True - - MDBoxLayout: - adaptive_size: True - pos_hint: {"right": 1} - padding: 0, 0, "8dp", "8dp" - spacing: "8dp" - - MDBoxLayout: - id: left_action_box - adaptive_size: True - - MDBoxLayout: - id: right_action_box - adaptive_size: True diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/banner.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/banner.py deleted file mode 100644 index 534783c..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/banner/banner.py +++ /dev/null @@ -1,439 +0,0 @@ -""" -Components/Banner -================= - -.. seealso:: - - `Material Design spec, Banner `_ - -.. rubric:: A banner displays a prominent message and related optional actions. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner.png - :align: center - -Usage -===== - -.. code-block:: python - - from kivy.lang import Builder - from kivy.factory import Factory - - from kivymd.app import MDApp - - Builder.load_string(''' - - - MDBanner: - id: banner - text: ["One line string text example without actions."] - # The widget that is under the banner. - # It will be shifted down to the height of the banner. - over_widget: screen - vertical_pad: toolbar.height - - MDTopAppBar: - id: toolbar - title: "Example Banners" - elevation: 4 - pos_hint: {'top': 1} - - MDBoxLayout: - id: screen - orientation: "vertical" - size_hint_y: None - height: Window.height - toolbar.height - - OneLineListItem: - text: "Banner without actions" - on_release: banner.show() - - Widget: - ''') - - - class Test(MDApp): - def build(self): - return Factory.ExampleBanner() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-example-1.gif - :align: center - -.. rubric:: Banner type. - -By default, the banner is of the type ``'one-line'``: - -.. code-block:: kv - - MDBanner: - text: ["One line string text example without actions."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-one-line.png - :align: center - -To use a two-line banner, specify the ``'two-line'`` :attr:`MDBanner.type` for the banner -and pass the list of two lines to the :attr:`MDBanner.text` parameter: - -.. code-block:: kv - - MDBanner: - type: "two-line" - text: - ["One line string text example without actions.", "This is the second line of the banner message."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-two-line.png - :align: center - -Similarly, create a three-line banner: - -.. code-block:: kv - - MDBanner: - type: "three-line" - text: - ["One line string text example without actions.", "This is the second line of the banner message.", "and this is the third line of the banner message."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-three-line.png - :align: center - -To add buttons to any type of banner, -use the :attr:`MDBanner.left_action` and :attr:`MDBanner.right_action` parameters, -which should take a list ['Button name', function]: - -.. code-block:: kv - - MDBanner: - text: ["One line string text example without actions."] - left_action: ["CANCEL", lambda x: None] - -Or two buttons: - -.. code-block:: kv - - MDBanner: - text: ["One line string text example without actions."] - left_action: ["CANCEL", lambda x: None] - right_action: ["CLOSE", lambda x: None] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-actions.png - :align: center - -If you want to use the icon on the left in the banner, -add the prefix `'-icon'` to the banner type: - -.. code-block:: kv - - MDBanner: - type: "one-line-icon" - icon: f"{images_path}/kivymd.png" - text: ["One line string text example without actions."] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/banner-icon.png - :align: center - -.. Note:: `See full example `_ -""" - -__all__ = ("MDBanner",) - -import os -from typing import Union - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BoundedNumericProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.widget import Widget - -from kivymd import uix_path -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDFlatButton -from kivymd.uix.card import MDCard -from kivymd.uix.list import ( - OneLineAvatarListItem, - OneLineListItem, - ThreeLineAvatarListItem, - ThreeLineListItem, - TwoLineAvatarListItem, - TwoLineListItem, -) - -with open( - os.path.join(uix_path, "banner", "banner.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDBanner(MDCard): - """ - Banner class. - - For more information, see in the :class:`~kivymd.uix.card.MDCard` - class documentation. - """ - - vertical_pad = NumericProperty(dp(68)) - """ - Indent the banner at the top of the screen. - - :attr:`vertical_pad` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(68)`. - """ - - opening_transition = StringProperty("in_quad") - """ - The name of the animation transition. - - :attr:`opening_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'in_quad'`. - """ - - icon = StringProperty("data/logo/kivy-icon-128.png") - """ - Icon banner. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'data/logo/kivy-icon-128.png'`. - """ - - over_widget = ObjectProperty() - """ - The widget that is under the banner. - It will be shifted down to the height of the banner. - - :attr:`over_widget` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - text = ListProperty() - """ - List of lines for banner text. - Must contain no more than three lines for a - `'one-line'`, `'two-line'` and `'three-line'` banner, respectively. - - :attr:`text` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - left_action = ListProperty() - """ - The action of banner. - - To add one action, make a list [`'name_action'`, callback] - where `'name_action'` is a string that corresponds to an action name and - ``callback`` is the function called on a touch release event. - - :attr:`left_action` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - right_action = ListProperty() - """ - Works the same way as :attr:`left_action`. - - :attr:`right_action` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - type = OptionProperty( - "one-line", - options=[ - "one-line", - "two-line", - "three-line", - "one-line-icon", - "two-line-icon", - "three-line-icon", - ], - allownone=True, - ) - """ - Banner type. . Available options are: (`"one-line"`, `"two-line"`, - `"three-line"`, `"one-line-icon"`, `"two-line-icon"`, `"three-line-icon"`). - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'one-line'`. - """ - - opening_timeout = BoundedNumericProperty(0.7, min=0.7) - """ - Time interval after which the banner will be shown. - - .. versionadded:: 1.0.0 - - :attr:`opening_timeout` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `0.7`. - """ - - opening_time = NumericProperty(0.15) - """ - The time taken for the banner to slide to the :attr:`state` `'open'`. - - .. versionadded:: 1.0.0 - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.15`. - """ - - closing_time = NumericProperty(0.15) - """ - The time taken for the banner to slide to the :attr:`state` `'close'`. - - .. versionadded:: 1.0.0 - - :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.15`. - """ - - _type_message = None - _progress = False - - def add_actions_buttons( - self, instance_box: MDBoxLayout, data: list - ) -> None: - """ - Adds buttons to the banner. - - :param data: ['NAME BUTTON', ]; - """ - - if data: - name_action_button, function_action_button = data - action_button = MDFlatButton( - text=f"[b]{name_action_button}[/b]", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - on_release=function_action_button, - ) - action_button.markup = True - instance_box.add_widget(action_button) - - def show(self) -> None: - """Displays a banner on the screen.""" - - def show(interval: Union[int, float]): - self.set_type_banner() - self.add_actions_buttons(self.ids.left_action_box, self.left_action) - self.add_actions_buttons( - self.ids.right_action_box, self.right_action - ) - self._add_banner_to_container() - Clock.schedule_once(self.animation_display_banner, 0.1) - - if not self._progress: - self._progress = True - if self.ids.container_message.children: - self.hide() - Clock.schedule_once(show, self.opening_timeout) - - def hide(self) -> None: - """Hides the banner from the screen.""" - - def hide(interval: Union[int, float]): - anim = Animation(banner_y=0, d=self.closing_time) - anim.bind(on_complete=self._remove_banner) - anim.start(self) - Animation( - y=self.over_widget.y + self.height, d=self.closing_time - ).start(self.over_widget) - - if not self._progress: - self._progress = True - Clock.schedule_once(hide, 0.5) - - def set_type_banner(self) -> None: - self._type_message = { - "three-line-icon": ThreeLineIconBanner, - "two-line-icon": TwoLineIconBanner, - "one-line-icon": OneLineIconBanner, - "three-line": ThreeLineBanner, - "two-line": TwoLineBanner, - "one-line": OneLineBanner, - }[self.type] - - def animation_display_banner(self, interval: Union[int, float]) -> None: - Animation( - banner_y=self.height + self.vertical_pad, - d=self.opening_time, - t=self.opening_transition, - ).start(self) - anim = Animation( - y=self.over_widget.y - self.height, - d=self.opening_time, - t=self.opening_transition, - ) - anim.bind(on_complete=self._reset_progress) - anim.start(self.over_widget) - - def _remove_banner(self, *args): - self.ids.container_message.clear_widgets() - self.ids.left_action_box.clear_widgets() - self.ids.right_action_box.clear_widgets() - self._reset_progress() - - def _reset_progress(self, *args): - self._progress = False - - def _add_banner_to_container(self) -> None: - self.ids.container_message.add_widget( - self._type_message(text_message=self.text, icon=self.icon) - ) - - -class BaseBanner(Widget): - """Implements the base banner class.""" - - text_message = ListProperty(["", "", ""]) - """ - List of banner strings. First, second and, respectively, third lines. - - :attr:`text_message` is an :class:`~kivy.properties.ListProperty` - and defaults to `['', '', '']`. - """ - - icon = StringProperty() - """ - Icon banner. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - def on_touch_down(self, touch): - self.parent.parent.hide() - - -class ThreeLineIconBanner(ThreeLineAvatarListItem, BaseBanner): - pass - - -class TwoLineIconBanner(TwoLineAvatarListItem, BaseBanner): - pass - - -class OneLineIconBanner(OneLineAvatarListItem, BaseBanner): - pass - - -class ThreeLineBanner(ThreeLineListItem, BaseBanner): - pass - - -class TwoLineBanner(TwoLineListItem, BaseBanner): - pass - - -class OneLineBanner(OneLineListItem, BaseBanner): - pass diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__init__.py index d80d8c1..5d26fef 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__init__.py @@ -5,21 +5,11 @@ Behaviors Modules and classes implementing various behaviors for buttons etc. """ -from .backgroundcolor_behavior import ( - BackgroundColorBehavior, - SpecificBackgroundColorBehavior, -) +from .backgroundcolor_behavior import BackgroundColorBehavior # flake8: NOQA from .declarative_behavior import DeclarativeBehavior -from .elevation import ( - CircularElevationBehavior, - CommonElevationBehavior, - FakeCircularElevationBehavior, - FakeRectangularElevationBehavior, - RectangularElevationBehavior, - RoundedRectangularElevationBehavior, -) +from .elevation import CommonElevationBehavior from .motion_behavior import ( MotionDialogBehavior, MotionShackBehavior, diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/__init__.cpython-311.pyc index df1b5c8..9260d30 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-311.pyc index d984cd1..5eafe9a 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/backgroundcolor_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/declarative_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/declarative_behavior.cpython-311.pyc index 73e7ad1..449c70b 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/declarative_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/declarative_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/elevation.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/elevation.cpython-311.pyc index 04af103..1c12590 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/elevation.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/elevation.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-311.pyc index d8e1a41..dc051c8 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/focus_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-311.pyc index f60dddd..53e78de 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/hover_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-311.pyc index 20f9881..98f07ed 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/magic_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/motion_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/motion_behavior.cpython-311.pyc index c667f1a..36b5d77 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/motion_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/motion_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-311.pyc index 811ebcb..63e0e6d 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/ripple_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/rotate_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/rotate_behavior.cpython-311.pyc index 2846959..35f386c 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/rotate_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/rotate_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/scale_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/scale_behavior.cpython-311.pyc index ddbf1b8..b1f16f0 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/scale_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/scale_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/state_layer_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/state_layer_behavior.cpython-311.pyc new file mode 100644 index 0000000..2fa0081 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/state_layer_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/stencil_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/stencil_behavior.cpython-311.pyc index aa28592..83ab380 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/stencil_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/stencil_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-311.pyc index 219c014..2a0b4a8 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/toggle_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-311.pyc index e24bbbf..7bcd10e 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/__pycache__/touch_behavior.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/backgroundcolor_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/backgroundcolor_behavior.py index a24166d..c9fe16c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/backgroundcolor_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/backgroundcolor_behavior.py @@ -7,7 +7,7 @@ Behaviors/Background Color from __future__ import annotations -__all__ = ("BackgroundColorBehavior", "SpecificBackgroundColorBehavior") +__all__ = ("BackgroundColorBehavior",) from kivy.animation import Animation from kivy.lang import Builder @@ -15,15 +15,10 @@ from kivy.properties import ( ColorProperty, ListProperty, NumericProperty, - OptionProperty, ReferenceListProperty, StringProperty, VariableListProperty, ) -from kivy.utils import get_color_from_hex - -from kivymd.color_definitions import hue, palette, text_colors -from kivymd.theming import ThemeManager Builder.load_string( """ @@ -37,8 +32,9 @@ Builder.load_string( angle: self.angle origin: self._background_origin Color: + group: "backgroundcolor-behavior-bg-color" rgba: self._md_bg_color - RoundedRectangle: + SmoothRoundedRectangle: group: "Background_instruction" size: self.size pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0) @@ -49,11 +45,17 @@ Builder.load_string( source: root.background Color: rgba: self.line_color if self.line_color else (0, 0, 0, 0) - # TODO: maybe we should use SmoothLine, - # but this should be tested on all widgets. - Line: + SmoothLine: width: root.line_width rounded_rectangle: + [ \ + 0, + 0, \ + self.width, \ + self.height, \ + *self.radius, \ + ] \ + if isinstance(self, RelativeLayout) else \ [ \ self.x, self.y, \ @@ -73,7 +75,7 @@ class BackgroundColorBehavior: Background image path. :attr:`background` is a :class:`~kivy.properties.StringProperty` - and defaults to `None`. + and defaults to `''`. """ radius = VariableListProperty([0], length=4) @@ -93,7 +95,7 @@ class BackgroundColorBehavior: # FIXME: in this case, we will not be able to animate this property # using the `Animation` class. - md_bg_color = ColorProperty([1, 1, 1, 0]) + md_bg_color = ColorProperty([0, 0, 0, 0]) """ The background color of the widget (:class:`~kivy.uix.widget.Widget`) that will be inherited from the :attr:`BackgroundColorBehavior` class. @@ -118,12 +120,12 @@ class BackgroundColorBehavior: md_bg_color: 0, 1, 1, 1 :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 0]`. + and defaults to `[0, 0, 0, 0]`. """ line_color = ColorProperty([0, 0, 0, 0]) """ - If a custom value is specified for the `line_color parameter`, the border + If a custom value is specified for the `line_color` parameter, the border of the specified color will be used to border the widget: .. code-block:: kv @@ -157,37 +159,18 @@ class BackgroundColorBehavior: _background_y = NumericProperty(0) _background_origin = ReferenceListProperty(_background_x, _background_y) _md_bg_color = ColorProperty([0, 0, 0, 0]) - _origin_line_color = ColorProperty(None) - _origin_md_bg_color = ColorProperty(None) def __init__(self, **kwarg): super().__init__(**kwarg) - self.bind( - pos=self.update_background_origin, - disabled=self.restore_color_origin, - ) + self.bind(pos=self.update_background_origin) - def restore_color_origin(self, instance_md_widget, value: bool) -> None: - """Called when the values of :attr:`disabled` change.""" - - if not value: - if self._origin_line_color: - self.line_color = self._origin_line_color - if self._origin_md_bg_color: - self.md_bg_color = self._origin_md_bg_color - - def on_line_color(self, instance_md_widget, value: list | str) -> None: - """Called when the values of :attr:`line_color` change.""" - - if not self.disabled: - self._origin_line_color = value - - def on_md_bg_color(self, instance_md_widget, color: list | str): - """Called when the values of :attr:`md_bg_color` change.""" + def on_md_bg_color(self, instance, color: list | str): + """Fired when the values of :attr:`md_bg_color` change.""" if ( hasattr(self, "theme_cls") and self.theme_cls.theme_style_switch_animation + and self.__class__.__name__ != "MDDropdownMenu" ): Animation( _md_bg_color=color, @@ -197,94 +180,10 @@ class BackgroundColorBehavior: else: self._md_bg_color = color - if not self.disabled: - self._origin_md_bg_color = color - - def update_background_origin(self, instance_md_widget, pos: list) -> None: - """Called when the values of :attr:`pos` change.""" + def update_background_origin(self, instance, pos: list) -> None: + """Fired when the values of :attr:`pos` change.""" if self.background_origin: self._background_origin = self.background_origin else: self._background_origin = self.center - - -class SpecificBackgroundColorBehavior(BackgroundColorBehavior): - background_palette = OptionProperty( - "Primary", options=["Primary", "Accent", *palette] - ) - """ - See :attr:`kivymd.color_definitions.palette`. - - :attr:`background_palette` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Primary'`. - """ - - background_hue = OptionProperty("500", options=hue) - """ - See :attr:`kivymd.color_definitions.hue`. - - :attr:`background_hue` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'500'`. - """ - - specific_text_color = ColorProperty([0, 0, 0, 0.87]) - """ - :attr:`specific_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.87]`. - """ - - specific_secondary_text_color = ColorProperty([0, 0, 0, 0.87]) - """ - :attr:`specific_secondary_text_color`is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.87]`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if hasattr(self, "theme_cls"): - self.theme_cls.bind( - primary_palette=self._update_specific_text_color, - accent_palette=self._update_specific_text_color, - theme_style=self._update_specific_text_color, - ) - self.bind( - background_hue=self._update_specific_text_color, - background_palette=self._update_specific_text_color, - ) - self._update_specific_text_color(None, None) - - def _update_specific_text_color( - self, instance_theme_manager: ThemeManager, theme_style: str - ) -> None: - if hasattr(self, "theme_cls"): - palette = { - "Primary": self.theme_cls.primary_palette, - "Accent": self.theme_cls.accent_palette, - }.get(self.background_palette, self.background_palette) - else: - palette = {"Primary": "Blue", "Accent": "Amber"}.get( - self.background_palette, self.background_palette - ) - color = get_color_from_hex(text_colors[palette][self.background_hue]) - secondary_color = color[:] - # Check for black text (need to adjust opacity). - if (color[0] + color[1] + color[2]) == 0: - color[3] = 0.87 - secondary_color[3] = 0.54 - else: - secondary_color[3] = 0.7 - - if ( - hasattr(self, "theme_cls") - and self.theme_cls.theme_style_switch_animation - ): - Animation( - specific_text_color=color, - specific_secondary_text_color=secondary_color, - d=self.theme_cls.theme_style_switch_animation_duration, - t="linear", - ).start(self) - else: - self.specific_text_color = color - self.specific_secondary_text_color = secondary_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/declarative_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/declarative_behavior.py index 0fb5eb7..de94093 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/declarative_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/declarative_behavior.py @@ -33,31 +33,35 @@ Imperative style .. code-block:: python from kivymd.app import MDApp - from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem - from kivymd.uix.label import MDLabel + from kivymd.uix.navigationbar import ( + MDNavigationBar, + MDNavigationItem, + MDNavigationItemIcon, + MDNavigationItemLabel, + ) from kivymd.uix.screen import MDScreen class Example(MDApp): def build(self): screen = MDScreen() - bottom_navigation = MDBottomNavigation( - panel_color="#eeeaea", - selected_color_background="#97ecf8", - text_color_active="white", - ) + bottom_navigation = MDNavigationBar() - data = { - "screen 1": {"text": "Mail", "icon": "gmail"}, - "screen 2": {"text": "Discord", "icon": "discord"}, - "screen 3": {"text": "LinkedIN", "icon": "linkedin"}, - } - for key in data.keys(): - text = data[key]["text"] - navigation_item = MDBottomNavigationItem( - name=key, text=text, icon=data[key]["icon"] + datas = [ + {"text": "Mail", "icon": "gmail"}, + {"text": "GitHub", "icon": "git"}, + {"text": "LinkedIN", "icon": "linkedin"}, + ] + for data in datas: + text = data["text"] + navigation_item = MDNavigationItem( + MDNavigationItemIcon( + icon=data["icon"], + ), + MDNavigationItemLabel( + text=text, + ), ) - navigation_item.add_widget(MDLabel(text=text, halign="center")) bottom_navigation.add_widget(navigation_item) screen.add_widget(bottom_navigation) @@ -66,9 +70,6 @@ Imperative style Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png - :align: center - Take a look at the above code example. This is a very simple UI. But looking at this code, you will not be able to figure the widget tree and understand which UI this code implements. This is named imperative programming style, @@ -87,51 +88,42 @@ Declarative style with KV language from kivymd.app import MDApp - class Test(MDApp): + class Example(MDApp): def build(self): return Builder.load_string( ''' MDScreen: - MDBottomNavigation: - panel_color: "#eeeaea" - selected_color_background: "#97ecf8" - text_color_active: "white" + MDNavigationBar: - MDBottomNavigationItem: - name: "screen 1" - text: "Mail" - icon: "gmail" + MDNavigationItem: - MDLabel: + MDNavigationItemIcon: + icon: "gmail" + + MDNavigationItemLabel: text: "Mail" - halign: "center" - MDBottomNavigationItem: - name: "screen 2" - text: "Discord" - icon: "discord" + MDNavigationItem: - MDLabel: - text: "Discord" - halign: "center" + MDNavigationItemIcon: + icon: "git" - MDBottomNavigationItem: - name: "screen 3" - text: "LinkedIN" - icon: "linkedin" + MDNavigationItemLabel: + text: "GitHub" - MDLabel: + MDNavigationItem: + + MDNavigationItemIcon: + icon: "linkedin" + + MDNavigationItemLabel: text: "LinkedIN" - halign: "center" ''' ) - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-styles-programming.png - :align: center + Example().run() Looking at this code, we can now clearly see the widget tree and their properties. We can quickly navigate through the components of the screen and quickly @@ -146,48 +138,41 @@ Declarative style with Python code .. code-block:: python from kivymd.app import MDApp - from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem - from kivymd.uix.label import MDLabel - from kivymd.uix.screen import MDScreen + from kivymd.uix.navigationbar import ( + MDNavigationBar, + MDNavigationItemIcon, + MDNavigationItem, + MDNavigationItemLabel, + ) class Example(MDApp): def build(self): - return ( - MDScreen( - MDBottomNavigation( - MDBottomNavigationItem( - MDLabel( - text="Mail", - halign="center", - ), - name="screen 1", - text="Mail", - icon="gmail", - ), - MDBottomNavigationItem( - MDLabel( - text="Discord", - halign="center", - ), - name="screen 2", - text="Discord", - icon="discord", - ), - MDBottomNavigationItem( - MDLabel( - text="LinkedIN", - halign="center", - ), - name="screen 3", - text="LinkedIN", - icon="linkedin", - ), - panel_color="#eeeaea", - selected_color_background="#97ecf8", - text_color_active="white", - ) - ) + return MDNavigationBar( + MDNavigationItem( + MDNavigationItemIcon( + icon="gmail", + ), + MDNavigationItemLabel( + text="Mail", + ), + ), + MDNavigationItem( + MDNavigationItemIcon( + icon="twitter", + ), + MDNavigationItemLabel( + text="Twitter", + ), + ), + MDNavigationItem( + MDNavigationItemIcon( + icon="linkedin", + ), + MDNavigationItemLabel( + text="LinkedIN", + ), + ), ) @@ -239,7 +224,7 @@ get to the desired id: from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDRaisedButton + from kivymd.uix.button import MDButton, MDButtonText from kivymd.uix.floatlayout import MDFloatLayout @@ -248,18 +233,22 @@ get to the desired id: return ( MDBoxLayout( MDFloatLayout( - MDRaisedButton( + MDButton( + MDButtonText( + text="Button 1", + ), id="button_1", - text="Button 1", pos_hint={"center_x": 0.5, "center_y": 0.5}, ), id="box_container_1", ), MDBoxLayout( MDFloatLayout( - MDRaisedButton( + MDButton( + MDButtonText( + text="Button 2", + ), id="button_2", - text="Button 2", pos_hint={"center_x": 0.5, "center_y": 0.5}, ), id="float_container", @@ -271,13 +260,13 @@ get to the desired id: def on_start(self): # { - # 'box_container_1': , - # 'box_container_2': + # 'button_1': , + # 'button_2': , + # 'float_container': , + # 'box_container_1': , + # 'box_container_2': , # } - print(self.root.ids) - - # - print(self.root.ids.box_container_2.ids.float_container.ids.button_2) + print(self.root.get_ids()) Example().run() @@ -293,6 +282,15 @@ from kivy.properties import StringProperty from kivy.uix.widget import Widget +class _Dict(dict): + """Implements access to dictionary values via a dot.""" + + def __getattr__(self, name): + return self[name] + + +# TODO: Add cleaning of the `__ids` collection when removing child widgets +# from the parent. class DeclarativeBehavior: """ Implements the creation and addition of child widgets as declarative @@ -307,6 +305,8 @@ class DeclarativeBehavior: and defaults to `''`. """ + __ids = _Dict() + def __init__(self, *args, **kwargs): super().__init__(**kwargs) @@ -314,4 +314,12 @@ class DeclarativeBehavior: if issubclass(child.__class__, Widget): self.add_widget(child) if hasattr(child, "id") and child.id: - self.ids[child.id] = child + self.__ids[child.id] = child + + def get_ids(self) -> dict: + """ + Returns a dictionary of widget IDs defined in Python + code that is written in a declarative style. + """ + + return self.__ids diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/elevation.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/elevation.py index f081d5c..ce219e1 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/elevation.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/elevation.py @@ -31,7 +31,7 @@ For example, let's create a button with a rectangular elevation effect: ) KV = ''' - + size_hint: None, None size: "250dp", "50dp" @@ -39,19 +39,19 @@ For example, let's create a button with a rectangular elevation effect: MDScreen: # With elevation effect - RectangularElevationButton: + ElevationWidget: pos_hint: {"center_x": .5, "center_y": .6} elevation: 4 shadow_offset: 0, -6 shadow_softness: 4 # Without elevation effect - RectangularElevationButton: + ElevationWidget: pos_hint: {"center_x": .5, "center_y": .4} ''' - class RectangularElevationButton( + class ElevationWidget( RectangularRippleBehavior, CommonElevationBehavior, ButtonBehavior, @@ -84,7 +84,7 @@ For example, let's create a button with a rectangular elevation effect: from kivymd.uix.screen import MDScreen - class RectangularElevationButton( + class ElevationWidget( RectangularRippleBehavior, CommonElevationBehavior, ButtonBehavior, @@ -101,13 +101,13 @@ For example, let's create a button with a rectangular elevation effect: def build(self): return ( MDScreen( - RectangularElevationButton( + ElevationWidget( pos_hint={"center_x": .5, "center_y": .6}, elevation=4, shadow_softness=4, shadow_offset=(0, -6), ), - RectangularElevationButton( + ElevationWidget( pos_hint={"center_x": .5, "center_y": .4}, ), ) @@ -271,7 +271,7 @@ Animating the elevation size: 100, 100 md_bg_color: 0, 0, 1, 1 elevation: 2 - radius: 18 + radius: dp(18) ''' @@ -306,6 +306,7 @@ Animating the elevation from kivy.animation import Animation from kivy.uix.behaviors import ButtonBehavior + from kivy.metrics import dp from kivymd.app import MDApp from kivymd.uix.behaviors import CommonElevationBehavior, RectangularRippleBehavior @@ -341,7 +342,7 @@ Animating the elevation size=(100, 100), md_bg_color="blue", elevation=2, - radius=18, + radius=dp(18), ) ) ) @@ -355,23 +356,17 @@ Animating the elevation from __future__ import annotations -__all__ = ( - "CommonElevationBehavior", - "RectangularElevationBehavior", - "CircularElevationBehavior", - "RoundedRectangularElevationBehavior", - "FakeRectangularElevationBehavior", - "FakeCircularElevationBehavior", -) +__all__ = ("CommonElevationBehavior",) -from kivy import Logger from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( BoundedNumericProperty, ColorProperty, ListProperty, NumericProperty, VariableListProperty, + DictProperty, ) from kivy.uix.widget import Widget @@ -393,18 +388,15 @@ Builder.load_string( axis: tuple(self.rotate_value_axis) origin: self.center Color: - rgba: - (0, 0, 0, 0) \ - if self.disabled or not self.elevation else \ - root.shadow_color + rgba: root.shadow_color BoxShadow: - pos: self.pos + pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0) size: self.size offset: root.shadow_offset spread_radius: -(root.shadow_softness), -(root.shadow_softness) - blur_radius: root.elevation * 10 + blur_radius: root.elevation_levels[root.elevation_level] border_radius: - (root.radius if hasattr(self, "radius") else [0, 0, 0, 0]) \ + (root.radius if hasattr(self, "radius") and root.radius else [0, 0, 0, 0]) \ if root.shadow_radius == [0.0, 0.0, 0.0, 0.0] else \ root.shadow_radius canvas.after: @@ -421,6 +413,36 @@ class CommonElevationBehavior(Widget): class documentation. """ + elevation_level = BoundedNumericProperty(0, min=0, max=5) + """ + Elevation level (values from 0 to 5) + + .. versionadded:: 1.2.0 + + :attr:`elevation_level` is an :class:`~kivy.properties.BoundedNumericProperty` + and defaults to `0`. + """ + + elevation_levels = DictProperty( + { + 0: 0, + 1: dp(8), + 2: dp(12), + 3: dp(16), + 4: dp(20), + 5: dp(24), + } + ) + """ + Elevation is measured as the distance between components along the z-axis + in density-independent pixels (dps). + + .. versionadded:: 1.2.0 + + :attr:`elevation_levels` is an :class:`~kivy.properties.DictProperty` + and defaults to `{0: dp(0), 1: dp(8), 2: dp(23), 3: dp(16), 4: dp(20), 5: dp(24)}`. + """ + elevation = BoundedNumericProperty(0, min=0, errorvalue=0) """ Elevation of the widget. @@ -449,7 +471,7 @@ class CommonElevationBehavior(Widget): MDScreen: MDCard: - radius: 12, 46, 12, 46 + radius: dp(12), dp(46), dp(12), dp(46) size_hint: .5, .3 pos_hint: {"center_x": .5, "center_y": .5} elevation: 2 @@ -486,26 +508,26 @@ class CommonElevationBehavior(Widget): from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior KV = ''' - + size_hint: None, None size: "250dp", "50dp" MDScreen: - RectangularElevationButton: + ElevationWidget: pos_hint: {"center_x": .5, "center_y": .6} elevation: 6 shadow_softness: 6 - RectangularElevationButton: + ElevationWidget: pos_hint: {"center_x": .5, "center_y": .4} elevation: 6 shadow_softness: 12 ''' - class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior): + class ElevationWidget(CommonElevationBehavior, BackgroundColorBehavior): def __init__(self, **kwargs): super().__init__(**kwargs) self.md_bg_color = "blue" @@ -522,19 +544,7 @@ class CommonElevationBehavior(Widget): :align: center :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` - and defaults to `12`. - """ - - shadow_softness_size = BoundedNumericProperty(2, min=2, deprecated=True) - """ - The value of the softness of the shadow. - - .. versionadded:: 1.1.0 - - .. deprecated:: 1.2.0 - - :attr:`shadow_softness_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2`. + and defaults to `0.0`. """ shadow_offset = ListProperty((0, 0)) @@ -551,23 +561,23 @@ class CommonElevationBehavior(Widget): from kivymd.uix.behaviors import BackgroundColorBehavior, CommonElevationBehavior KV = ''' - + size_hint: None, None size: "100dp", "100dp" MDScreen: - RectangularElevationButton: + ElevationWidget: pos_hint: {"center_x": .5, "center_y": .5} elevation: 6 - shadow_radius: 6 + shadow_radius: dp(6) shadow_softness: 12 shadow_offset: -12, -12 ''' - class RectangularElevationButton(CommonElevationBehavior, BackgroundColorBehavior): + class ElevationWidget(CommonElevationBehavior, BackgroundColorBehavior): def __init__(self, **kwargs): super().__init__(**kwargs) self.md_bg_color = "blue" @@ -585,7 +595,7 @@ class CommonElevationBehavior(Widget): .. code-block:: kv - RectangularElevationButton: + ElevationWidget: shadow_offset: 12, -12 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-2.png @@ -593,7 +603,7 @@ class CommonElevationBehavior(Widget): .. code-block:: kv - RectangularElevationButton: + ElevationWidget: shadow_offset: 12, 12 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-3.png @@ -601,7 +611,7 @@ class CommonElevationBehavior(Widget): .. code-block:: kv - RectangularElevationButton: + ElevationWidget: shadow_offset: -12, 12 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-offset-4.png @@ -619,7 +629,7 @@ class CommonElevationBehavior(Widget): .. code-block:: python - RectangularElevationButton: + ElevationWidget: shadow_color: 0, 0, 1, .8 .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-color.png @@ -691,82 +701,10 @@ class CommonElevationBehavior(Widget): and defaults to `(0, 0, 1)`. """ - _elevation = 0 + # _elevation = 0 + _elevation_level = 0 + _shadow_softness = 0 + _shadow_color = (0, 0, 0, 0) - def on_elevation(self, instance, value) -> None: - self._elevation = value - - -class RectangularElevationBehavior(CommonElevationBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~CommonElevationBehavior` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `RectangularElevationBehavior` class has been deprecated. " - "Use the `CommonElevationBehavior` class instead.`" - ) - - -class CircularElevationBehavior(CommonElevationBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~CommonElevationBehavior` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `CircularElevationBehavior` class has been deprecated. " - "Use the `CommonElevationBehavior` class instead.`" - ) - - -class RoundedRectangularElevationBehavior(CommonElevationBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~CommonElevationBehavior` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `RoundedRectangularElevationBehavior` class has been " - "deprecated. Use the `CommonElevationBehavior` class instead.`" - ) - - -class FakeRectangularElevationBehavior(CommonElevationBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~CommonElevationBehavior` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `FakeRectangularElevationBehavior` class has been " - "deprecated. Use the `CommonElevationBehavior` class instead." - ) - - -class FakeCircularElevationBehavior(CommonElevationBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~CommonElevationBehavior` class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `FakeCircularElevationBehavior` class has been deprecated. " - "Use the `CommonElevationBehavior` class instead." - ) + # def on_elevation(self, instance, value) -> None: + # self._elevation = value diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/focus_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/focus_behavior.py index 33e00bd..d9ad37e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/focus_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/focus_behavior.py @@ -15,7 +15,7 @@ Usage from kivy.lang import Builder from kivymd.app import MDApp - from kivymd.uix.behaviors import RectangularElevationBehavior + from kivymd.uix.behaviors import CommonElevationBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.behaviors.focus_behavior import FocusBehavior @@ -36,7 +36,7 @@ Usage ''' - class FocusWidget(MDBoxLayout, RectangularElevationBehavior, FocusBehavior): + class FocusWidget(MDBoxLayout, CommonElevationBehavior, FocusBehavior): pass @@ -65,25 +65,26 @@ Color change at focus/defocus __all__ = ("FocusBehavior",) -from kivy.app import App from kivy.properties import BooleanProperty, ColorProperty -from kivy.uix.behaviors import ButtonBehavior from kivymd.uix.behaviors import HoverBehavior -class FocusBehavior(HoverBehavior, ButtonBehavior): +class FocusBehavior(HoverBehavior): """ Focus behavior class. - For more information, see in the :class:`~kivymd.uix.behavior.HoverBehavior` - and :class:`~kivy.uix.button.ButtonBehavior` classes documentation. + For more information, see in the + :class:`~kivymd.uix.behavior.HoverBehavior` and + :class:`~kivy.uix.button.ButtonBehavior` + classes documentation. :Events: :attr:`on_enter` - Called when mouse enters the bbox of the widget AND the widget is visible + Fired when mouse enters the bbox of the widget AND the widget is + visible. :attr:`on_leave` - Called when the mouse exits the widget AND the widget is visible + Fired when the mouse exits the widget AND the widget is visible. """ focus_behavior = BooleanProperty(True) @@ -109,39 +110,3 @@ class FocusBehavior(HoverBehavior, ButtonBehavior): :attr:`unfocus_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - - def on_enter(self): - """Called when mouse enter the bbox of the widget.""" - - if ( - hasattr(self, "md_bg_color") or hasattr(self, "bg_color") - ) and self.focus_behavior: - if hasattr(self, "theme_cls") and not self.focus_color: - color = self.theme_cls.bg_normal - else: - if not self.focus_color: - color = App.get_running_app().theme_cls.bg_normal - else: - color = self.focus_color - self._set_bg_color(color) - - def on_leave(self): - """Called when the mouse exit the widget.""" - - if ( - hasattr(self, "md_bg_color") or hasattr(self, "bg_color") - ) and self.focus_behavior: - if hasattr(self, "theme_cls") and not self.unfocus_color: - color = self.theme_cls.bg_light - else: - if not self.unfocus_color: - color = App.get_running_app().theme_cls.bg_light - else: - color = self.unfocus_color - self._set_bg_color(color) - - def _set_bg_color(self, color): - if hasattr(self, "md_bg_color"): - self.md_bg_color = color - elif hasattr(self, "bg_color"): - self.bg_color = color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/hover_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/hover_behavior.py index dbe1ce2..e207d03 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/hover_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/hover_behavior.py @@ -27,9 +27,13 @@ the widget. .. note:: - :class:`~HoverBehavior` will by default check to see if the current Widget is visible (i.e. not covered by a modal or popup and not a part of a Relative Layout, MDTab or Carousel that is not currently visible etc) and will only issue events if the widget is visible. + :class:`~HoverBehavior` will by default check to see if the current Widget + is visible (i.e. not covered by a modal or popup and not a part of a + RelativeLayout, MDTab or Carousel that is not currently visible etc) + and will only issue events if the widget is visible. - To get the legacy behavior that the events are always triggered, you can set `detect_visible` on the Widget to `False`. + To get the legacy behavior that the events are always triggered, you can + set `detect_visible` on the Widget to `False`. .. code-block:: python @@ -40,13 +44,14 @@ the widget. from kivymd.uix.boxlayout import MDBoxLayout KV = ''' - Screen + MDScreen + md_bg_color: self.theme_cls.backgroundColor MDBoxLayout: id: box pos_hint: {'center_x': .5, 'center_y': .5} size_hint: .8, .8 - md_bg_color: app.theme_cls.bg_darkest + md_bg_color: self.theme_cls.secondaryContainerColor ''' @@ -54,19 +59,23 @@ the widget. '''Custom item implementing hover behavior.''' def on_enter(self, *args): - '''The method will be called when the mouse cursor - is within the borders of the current widget.''' + ''' + The method will be called when the mouse cursor + is within the borders of the current widget. + ''' - self.md_bg_color = (1, 1, 1, 1) + self.md_bg_color = "white" def on_leave(self, *args): - '''The method will be called when the mouse cursor goes beyond - the borders of the current widget.''' + ''' + The method will be called when the mouse cursor goes beyond + the borders of the current widget. + ''' - self.md_bg_color = self.theme_cls.bg_darkest + self.md_bg_color = self.theme_cls.secondaryContainerColor - class Test(MDApp): + class Example(MDApp): def build(self): self.screen = Builder.load_string(KV) for i in range(5): @@ -74,10 +83,9 @@ the widget. return self.screen - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hover-behavior.gif - :width: 250 px :align: center """ @@ -85,23 +93,25 @@ __all__ = ("HoverBehavior",) from kivy.core.window import Window from kivy.properties import BooleanProperty, ObjectProperty +from kivy.uix.relativelayout import RelativeLayout from kivy.uix.widget import Widget -class HoverBehavior(object): +class HoverBehavior: """ :Events: :attr:`on_enter` - Called when mouse enters the bbox of the widget AND the widget is visible + Fired when mouse enters the bbox of the widget and the widget is + visible. :attr:`on_leave` - Called when the mouse exits the widget AND the widget is visible + Fired when the mouse exits the widget and the widget is visible. """ hovering = BooleanProperty(False) """ `True`, if the mouse cursor is within the borders of the widget. - Note that this is set and cleared even if the widget is not visible + Note that this is set and cleared even if the widget is not visible. :attr:`hover` is a :class:`~kivy.properties.BooleanProperty` and defaults to `False`. @@ -109,7 +119,7 @@ class HoverBehavior(object): hover_visible = BooleanProperty(False) """ - `True` if hovering is True AND is the current widget is visible + `True` if hovering is `True` and is the current widget is visible. :attr:`hover_visible` is a :class:`~kivy.properties.BooleanProperty` and defaults to `False`. @@ -118,7 +128,7 @@ class HoverBehavior(object): enter_point = ObjectProperty(allownone=True) """ Holds the last position where the mouse pointer crossed into the Widget - if the Widget is visible and is currently in a hovering state + if the Widget is visible and is currently in a hovering state. :attr:`enter_point` is a :class:`~kivy.properties.ObjectProperty` and defaults to `None`. @@ -132,22 +142,24 @@ class HoverBehavior(object): and defaults to `True`. """ - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): self.register_event_type("on_enter") self.register_event_type("on_leave") Window.bind(mouse_pos=self.on_mouse_update) - super(HoverBehavior, self).__init__(**kwargs) + super().__init__(*args, **kwargs) def on_mouse_update(self, *args): - # If the Widget currently has no parent, do nothing + # If the Widget currently has no parent, do nothing. if not self.get_root_window(): return pos = args[1] - # - # is the pointer in the same position as the widget? - # If not - then issue an on_exit event if needed - # - if not self.collide_point(*self.to_widget(*pos)): + # Is the pointer in the same position as the widget? + # If not - then issue an on_exit event if needed. + if not self.collide_point( + *self.to_widget(*pos) + if not isinstance(self, RelativeLayout) + else (pos[0], pos[1]) + ): self.hovering = False self.enter_point = None if self.hover_visible: @@ -155,72 +167,59 @@ class HoverBehavior(object): self.dispatch("on_leave") return - # - # The pointer is in the same position as the widget - # - + # The pointer is in the same position as the widget. if self.hovering: - # - # nothing to do here. Not - this does not handle the case where - # a popup comes over an existing hover event. - # This seems reasonable - # + # Nothing to do here. Not - this does not handle the case where + # a popup comes over an existing hover event. + # This seems reasonable. return - # # Otherwise - set the hovering attribute - # self.hovering = True - # - # We need to traverse the tree to see if the Widget is visible - # - # This is a two stage process: - # - first go up the tree to the root Window. - # At each stage - check that the Widget is actually visible - # - Second - At the root Window check that there is not another branch - # covering the Widget - # - + # We need to traverse the tree to see if the Widget is visible. + # This is a two stage process - first go up the tree to the root. + # Window. At each stage - check that the Widget is actually visible. + # Second - at the root Window check that there is not another branch + # covering the Widget. self.hover_visible = True + if self.detect_visible: widget: Widget = self while True: - # Walk up the Widget tree from the target Widget + # Walk up the Widget tree from the target Widget. parent = widget.parent try: # See if the mouse point collides with the parent - # using both local and glabal coordinates to cover absoluet and relative layouts + # using both local and global coordinates to cover absolute + # and relative layouts. pinside = parent.collide_point( *parent.to_widget(*pos) ) or parent.collide_point(*pos) except Exception: - # The collide_point will error when you reach the root Window + # The collide_point will error when you reach the root + # Window. break if not pinside: self.hover_visible = False break - # Iterate upwards + # Iterate upwards. widget = parent - # # parent = root window # widget = first Widget on the current branch - # - children = parent.children for child in children: - # For each top level widget - check if is current branch + # For each top level widget - check if is current branch. # If it is - then break. - # If not then - since we start at 0 - this widget is visible - # - # Check to see if it should take the hover - # + # If not then - since we start at 0 - this widget is visible. + # Check to see if it should take the hover. if child == widget: - # this means that the current widget is visible + # This means that the current widget is visible. break if child.collide_point(*pos): - # this means that the current widget is covered by a modal or popup + # This means that the current widget is covered by a modal + # or popup. self.hover_visible = False break if self.hover_visible: @@ -228,7 +227,7 @@ class HoverBehavior(object): self.dispatch("on_enter") def on_enter(self): - """Called when mouse enters the bbox of the widget AND the widget is visible.""" + """Fired when mouse enter the bbox of the widget.""" def on_leave(self): - """Called when the mouse exits the widget AND the widget is visible.""" + """Fired when the mouse goes outside the widget border.""" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/motion_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/motion_behavior.py index 044970f..5616c32 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/motion_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/motion_behavior.py @@ -15,17 +15,20 @@ as dialogs, dropdown menu, snack bars, and so on. __all__ = ( "MotionBase", + "MotionExtendedFabButtonBehavior", "MotionDropDownMenuBehavior", "MotionDialogBehavior", "MotionShackBehavior", + "MotionDatePickerBehavior", + "MotionTimePickerBehavior", ) from kivy.animation import Animation from kivy.clock import Clock from kivy.core.window import Window -from kivy.properties import StringProperty, NumericProperty - -from kivymd.uix.behaviors.stencil_behavior import StencilBehavior +from kivy.metrics import dp +from kivy.properties import StringProperty, NumericProperty, ObjectProperty +from kivymd.uix.behaviors.scale_behavior import ScaleBehavior class MotionBase: @@ -166,80 +169,288 @@ class MotionDropDownMenuBehavior(MotionBase): self.scale_value_y = value -class MotionDialogBehavior(MotionBase): +class MotionExtendedFabButtonBehavior(MotionBase): """ - Base class for dialog movement behavior. + Base class for extended Fab button movement behavior. For more information, see in the :class:`~MotionBase` class documentation. """ - show_duration = NumericProperty(0.1) + show_transition = StringProperty("out_circ") + """ + The type of transition of the widget opening. + + :attr:`show_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'out_circ'`. + """ + + shift_transition = StringProperty("out_sine") + """ + Text label transition. + + :attr:`shift_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'out_sine'`. + """ + + show_duration = NumericProperty(0.3) """ Duration of widget display transition. :attr:`show_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.1`. + and defaults to `0.3`. """ - scale_x = NumericProperty(1.5) + hide_transition = StringProperty("out_sine") """ - Default X-axis scaling values. + The type of transition of the widget closing. - :attr:`scale_x` is a :class:`~kivy.properties.NumericProperty` - and defaults to `1.5`. + :attr:`hide_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'linear'`. """ - scale_y = NumericProperty(1.5) + hide_duration = NumericProperty(0.2) """ - Default Y-axis scaling values. + Duration of widget closing transition. - :attr:`scale_y` is a :class:`~kivy.properties.NumericProperty` - and defaults to `1.5`. + :attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.set_default_values() + _x = NumericProperty(0) + _anim_opacity = None - def set_default_values(self): - """Sets default scaled and transparency values.""" + def collapse(self, *args) -> None: + """Collapses the button.""" - self.scale_value_x = self.scale_x - self.scale_value_y = self.scale_y - self.opacity = 0 + def collapse(*args): + if self._label and self._icon: + Animation(_x=0, d=self.hide_duration).start(self) + anim = Animation( + width=dp(56), d=self.hide_duration, t=self.hide_transition + ) + anim.bind(on_progress=self._check_collapse_progress) + anim.start(self) + + Clock.schedule_once(collapse) + + def expand(self, *args) -> None: + """Expands the button.""" + + def expand(*args): + if self._label and self._icon: + anim = Animation( + width=self.width + + self._label.texture_size[0] + + (dp(18) if self._icon else 0), + d=self.show_duration, + t=self.show_transition, + ) + anim.bind(on_progress=self._check_expand_progress) + anim.start(self) + Animation( + _x=dp(12), d=self.show_duration, t=self.shift_transition + ).start(self) + + Clock.schedule_once(expand) + + def set_opacity_text_button(self, value: int) -> None: + if self._label: + self._anim_opacity = Animation( + opacity=value, + d=self.show_duration * 16.666666666666668 / 100 + if value + else self.show_duration * 1.6666666666666667 / 100, + ) + self._anim_opacity.bind( + on_complete=lambda *x: setattr(self, "_anim_opacity", None) + ) + self._anim_opacity.start(self._label) + + def _check_collapse_progress(self, animation, instance, progress) -> None: + if progress > 0.1: + if not self._anim_opacity: + self.set_opacity_text_button(0) + + def _check_expand_progress(self, animation, instance, progress) -> None: + if progress > 0.3: + if not self._anim_opacity: + self.set_opacity_text_button(1) + + +class MotionDialogBehavior(ScaleBehavior, MotionBase): + """ + Base class for dialog movement behavior. + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` + :class:`~MotionBase` + classes documentation. + """ + + show_transition = StringProperty("out_expo") + """ + The type of transition of the widget opening. + + :attr:`show_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'out_expo'`. + """ + + show_button_container_transition = StringProperty("out_circ") + """ + The type of transition of the widget opening. + + :attr:`show_button_container_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'out_circ'`. + """ + + hide_transition = StringProperty("out_circ") + """ + The type of transition of the widget opening. + + :attr:`show_transition` is a :class:`~kivy.properties.StringProperty` + and defaults to `'hide_transition'`. + """ + + show_duration = NumericProperty(0.4) + """ + Duration of widget display transition. + + :attr:`show_duration` is a :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ def on_dismiss(self, *args): - """Called when a dialog closed.""" + """Fired when a dialog closed.""" - self.set_default_values() + def remove_dialog(*args): + Window.remove_widget(self) + if self._scrim: + Window.remove_widget(self._scrim) - def on_open(self, *args): - """Called when a dialog opened.""" + if self._scrim: + Animation(alpha=0, d=self.hide_duration).start(self._scrim) Animation( - opacity=1, - scale_value_x=1, - scale_value_y=1, - t=self.show_transition, - d=self.show_duration, - ).start(self) + y=self.ids.content_container.y, + t=self.hide_transition, + d=self.hide_duration, + ).start(self.ids.button_container) + + anim = Animation( + opacity=0, + scale_value_y=0, + t=self.hide_transition, + d=self.hide_duration, + ) + anim.bind(on_complete=remove_dialog) + anim.start(self) + + def on_open(self, *args): + """Fired when a dialog opened.""" + + def open(*args): + self.scale_value_y = 0 + self.scale_value_center = (0, self.center[1] + self.height / 2) + Animation( + opacity=1, + scale_value_y=1, + t=self.show_transition, + d=self.show_duration, + ).start(self) + + Animation( + y=dp(24), + t=self.show_button_container_transition, + d=self.show_duration + 0.15, + ).start(self.ids.button_container) + + if self._scrim: + Animation(alpha=0.4, d=self.show_duration).start(self._scrim) + + Clock.schedule_once(open) -class MotionShackBehavior(StencilBehavior, MotionBase): +class MotionPickerBehavior(MotionDialogBehavior): + """ + Base class for date/time pickers movement behavior. + + For more information, see in the + :class:`~MotionDialogBehavior` class documentation. + """ + + _scrim = ObjectProperty() + + def on_open(self, *args): + """Fired when a dialog opened.""" + + def open(*args): + self.scale_value_y = 0 + self.scale_value_center = (0, self.center[1] + self.height / 2) + Animation( + opacity=1, + scale_value_y=1, + t=self.show_transition, + d=self.show_duration, + ).start(self) + + if self._scrim: + Animation(alpha=0.4, d=self.show_duration).start(self._scrim) + + Clock.schedule_once(open) + + def on_dismiss(self, *args): + """Fired when a dialog closed.""" + + def remove_dialog(*args): + Window.remove_widget(self) + if self._scrim: + Window.remove_widget(self._scrim) + self.dispatch("on_dismiss") + + if self._scrim: + Animation(alpha=0, d=self.hide_duration).start(self._scrim) + + anim = Animation( + opacity=0, + scale_value_y=0, + t=self.hide_transition, + d=self.hide_duration, + ) + anim.bind(on_complete=remove_dialog) + anim.start(self) + + +class MotionTimePickerBehavior(MotionPickerBehavior): + """ + Base class for time picker movement behavior. + + For more information, see in the + :class:`~MotionPickerBehavior` class documentation. + """ + + +class MotionDatePickerBehavior(MotionPickerBehavior): + """ + Base class for date picker movement behavior. + + For more information, see in the + :class:`~MotionPickerBehavior` class documentation. + """ + + +class MotionShackBehavior(MotionBase): """ The base class for the behavior of the movement of snack bars. For more information, see in the - :class:`~MotionBase` class and - :class:`~kivy.uix.behaviors.stencil_behavior.StencilBehavior` class - documentation. + :class:`~MotionBase` class documentation. """ _interval = 0 _height = 0 def on_dismiss(self, *args): - """Called when a snackbar closed.""" + """Fired when a snackbar closed.""" def remove_snackbar(*args): Window.parent.remove_widget(self) @@ -257,7 +468,7 @@ class MotionShackBehavior(StencilBehavior, MotionBase): anim.start(self) def on_open(self, *args): - """Called when a snackbar opened.""" + """Fired when a snackbar opened.""" def open(*args): self._height = self.height @@ -274,9 +485,9 @@ class MotionShackBehavior(StencilBehavior, MotionBase): ) ) anim.start(self) + self.dispatch("on_open") - Clock.schedule_once(open) - self.dispatch("on_open") + Clock.schedule_once(open, 0.2) def _wait_interval(self, interval): self._interval += interval diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/ripple_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/ripple_behavior.py index fddcef7..1d057ea 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/ripple_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/ripple_behavior.py @@ -285,11 +285,18 @@ class CommonRipple: and defaults to `'ripple_func_out'`. """ + ripple_effect = BooleanProperty(True) + """ + Should I use the ripple effect. + + :attr:`ripple_effect` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + _ripple_rad = NumericProperty() _doing_ripple = BooleanProperty(False) _finishing_ripple = BooleanProperty(False) _fading_out = BooleanProperty(False) - _no_ripple_effect = BooleanProperty(False) _round_rad = ListProperty([0, 0, 0, 0]) def lay_canvas_instructions(self) -> NoReturn: @@ -333,6 +340,8 @@ class CommonRipple: anim.start(self) def anim_complete(self, *args) -> None: + """Fired when the "fade_out" animation complete.""" + self._doing_ripple = False self._finishing_ripple = False self._fading_out = False @@ -378,7 +387,7 @@ class CommonRipple: if self.ripple_color: pass elif hasattr(self, "theme_cls"): - self.ripple_color = self.theme_cls.ripple_color + self.ripple_color = self.theme_cls.rippleColor else: # If no theme, set Gray 300. self.ripple_color = [ @@ -429,7 +438,11 @@ class RectangularRippleBehavior(CommonRipple): """ def lay_canvas_instructions(self) -> None: - if self._no_ripple_effect: + """ + Adds graphic instructions to the canvas to implement ripple animation. + """ + + if not self.ripple_effect: return with self.canvas.after if self.ripple_canvas_after else self.canvas.before: @@ -493,7 +506,7 @@ class CircularRippleBehavior(CommonRipple): """ def lay_canvas_instructions(self) -> None: - if self._no_ripple_effect: + if not self.ripple_effect: return with self.canvas.after if self.ripple_canvas_after else self.canvas.before: diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/state_layer_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/state_layer_behavior.py new file mode 100644 index 0000000..e13aa4a --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/state_layer_behavior.py @@ -0,0 +1,537 @@ +# TODO: Add docs. + +""" +Behaviors/State Layer +===================== + +.. seealso:: + + `Material Design spec, State layers `_ +""" + +from kivy import platform +from kivy.lang import Builder +from kivy.properties import ColorProperty, NumericProperty + +from kivymd.uix.behaviors.focus_behavior import FocusBehavior + + +Builder.load_string( + """ + + canvas.after: + Color + rgba: self.state_layer_color + RoundedRectangle: + group: "State_layer_instruction" + size: self.size + pos: self.pos + radius: self.radius if hasattr(self, "radius") else [0, ] +""", + filename="StateLayerBehavior.kv", +) + +# TODO: Add methods `set_text_color` and `set_icon_color` +# (methods that set the color of text and icons in the state +# `on_enter` and `on_leave` and `pressed`). + + +class StateLayerBehavior(FocusBehavior): + state_layer_color = ColorProperty([0, 0, 0, 0]) + """ + The color of the layer state. + + :attr:`state_layer_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0]`. + """ + + state_hover = NumericProperty(0.08) + """ + The transparency level of the layer as a percentage when hovering. + + :attr:`state_hover` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.08`. + """ + + state_press = NumericProperty(0.12) + """ + The transparency level of the layer as a percentage when pressed. + + :attr:`state_press` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.12`. + """ + + state_drag = NumericProperty(0.16) + """ + The transparency level of the layer as a percentage when dragged. + + :attr:`state_drag` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.16`. + """ + + # The transparency value of the disabled state. + # These values are specified in the M3 specification. + + # ------------------------------------------------------------------------- + # MDIconButton + # ------------------------------------------------------------------------- + + # Filled. + icon_button_filled_opacity_value_disabled_container = NumericProperty(0.12) + icon_button_filled_opacity_value_disabled_icon = NumericProperty(0.38) + + # Tonal. + icon_button_tonal_opacity_value_disabled_container = NumericProperty(0.12) + icon_button_tonal_opacity_value_disabled_icon = NumericProperty(0.38) + + # Outlined. + icon_button_outlined_opacity_value_disabled_container = NumericProperty( + 0.12 + ) + icon_button_outlined_opacity_value_disabled_line = NumericProperty(0.12) + icon_button_outlined_opacity_value_disabled_icon = NumericProperty(0.38) + + # Standard. + icon_button_standard_opacity_value_disabled_icon = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDFabButton + # ------------------------------------------------------------------------- + + fab_button_opacity_value_disabled_container = NumericProperty(0.12) + fab_button_opacity_value_disabled_icon = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDButton + # ------------------------------------------------------------------------- + + # Filled. + button_filled_opacity_value_disabled_container = NumericProperty(0.12) + button_filled_opacity_value_disabled_icon = NumericProperty(0.38) + button_filled_opacity_value_disabled_text = NumericProperty(0.38) + + # Tonal. + button_tonal_opacity_value_disabled_container = NumericProperty(0.12) + button_tonal_opacity_value_disabled_icon = NumericProperty(0.38) + button_tonal_opacity_value_disabled_text = NumericProperty(0.38) + + # Outlined. + button_outlined_opacity_value_disabled_container = NumericProperty(0.12) + button_outlined_opacity_value_disabled_line = NumericProperty(0.12) + button_outlined_opacity_value_disabled_icon = NumericProperty(0.38) + button_outlined_opacity_value_disabled_text = NumericProperty(0.38) + + # Elevated. + button_elevated_opacity_value_disabled_container = NumericProperty(0.12) + button_elevated_opacity_value_disabled_icon = NumericProperty(0.38) + button_elevated_opacity_value_disabled_text = NumericProperty(0.38) + + # Text. + button_text_opacity_value_disabled_icon = NumericProperty(0.38) + button_text_opacity_value_disabled_text = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDLabel + # ------------------------------------------------------------------------- + + label_opacity_value_disabled_text = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDCard + # ------------------------------------------------------------------------- + + card_filled_opacity_value_disabled_state_container = NumericProperty(0.38) + card_outlined_opacity_value_disabled_state_container = NumericProperty(0.12) + card_opacity_value_disabled_state_elevated_container = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDSegmentedButton + # ------------------------------------------------------------------------- + + segmented_button_opacity_value_disabled_container = NumericProperty(0.12) + segmented_button_opacity_value_disabled_container_active = NumericProperty( + 0.38 + ) + segmented_button_opacity_value_disabled_line = NumericProperty(0.12) + segmented_button_opacity_value_disabled_icon = NumericProperty(0.38) + segmented_button_opacity_value_disabled_text = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDChip + # ------------------------------------------------------------------------- + + chip_opacity_value_disabled_container = NumericProperty(0.12) + chip_opacity_value_disabled_text = NumericProperty(0.38) + chip_opacity_value_disabled_icon = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDSwitch + # ------------------------------------------------------------------------- + + switch_opacity_value_disabled_line = NumericProperty(0.12) + switch_opacity_value_disabled_container = NumericProperty(0.12) + switch_thumb_opacity_value_disabled_container = NumericProperty(0.38) + switch_opacity_value_disabled_icon = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDCheckbox + # ------------------------------------------------------------------------- + + checkbox_opacity_value_disabled_container = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # List + # ------------------------------------------------------------------------- + + list_opacity_value_disabled_container = NumericProperty(0.38) + list_opacity_value_disabled_leading_avatar = NumericProperty(0.38) + + # ------------------------------------------------------------------------- + # MDTextField + # ------------------------------------------------------------------------- + + text_field_filled_opacity_value_disabled_state_container = NumericProperty( + 0.18 + ) + text_field_outlined_opacity_value_disabled_state_container = ( + NumericProperty(0) + ) + text_field_opacity_value_disabled_max_length_label = NumericProperty(0.60) + text_field_opacity_value_disabled_helper_text_label = NumericProperty(0.60) + text_field_opacity_value_disabled_hint_text_label = NumericProperty(0.60) + text_field_opacity_value_disabled_leading_icon = NumericProperty(0.60) + text_field_opacity_value_disabled_trailing_icon = NumericProperty(0.60) + text_field_opacity_value_disabled_line = NumericProperty(0.12) + + _state = 0.0 + _bg_color = (0, 0, 0, 0) + _is_already_disabled = False + _shadow_softness = [0, 0] + _elevation_level = 0 + + # def __init__(self, *args, **kwargs): + # super().__init__(*args, **kwargs) + + def set_properties_widget(self) -> None: + """Fired `on_release/on_press/on_enter/on_leave` events.""" + + if not self.disabled: + self._restore_properties() + self._set_state_layer_color() + + def on_disabled(self, instance, value) -> None: + """Fired when the `disabled` value changes.""" + + from kivymd.uix.card import MDCard + from kivymd.uix.button import ( + MDIconButton, + MDButton, + MDFabButton, + MDExtendedFabButton, + ) + from kivymd.uix.segmentedbutton import MDSegmentedButtonItem + from kivymd.uix.segmentedbutton.segmentedbutton import ( + MDSegmentButtonSelectedIcon, + ) + from kivymd.uix.selectioncontrol import MDSwitch + from kivymd.uix.list import BaseListItem + from kivymd.uix.textfield import MDTextField + + if value and not self._is_already_disabled: + self._is_already_disabled = True + if isinstance(self, MDCard): + self.state_layer_color = ( + { + "filled": self.theme_cls.surfaceColor[:-1] + + [ + self.card_filled_opacity_value_disabled_state_container + ], + "outlined": self.theme_cls.outlineColor[:-1] + + [ + self.card_outlined_opacity_value_disabled_state_container + ], + "elevated": self.theme_cls.surfaceVariantColor[:-1] + + [ + self.card_opacity_value_disabled_state_elevated_container + ], + }[self.style] + if not self.md_bg_color_disabled + else self.md_bg_color_disabled + ) + elif isinstance(self, MDIconButton): + self.state_layer_color = ( + { + "tonal": self.theme_cls.onSurfaceColor[:-1] + + [ + self.icon_button_tonal_opacity_value_disabled_container + ], + "filled": self.theme_cls.onSurfaceColor[:-1] + + [ + self.icon_button_filled_opacity_value_disabled_container + ], + "outlined": self.theme_cls.onSurfaceColor[:-1] + + [ + self.icon_button_outlined_opacity_value_disabled_container + ], + "standard": self.theme_cls.transparentColor, + }[self.style] + if not self.md_bg_color_disabled + else self.md_bg_color_disabled + ) + elif isinstance(self, MDButton): + self.state_layer_color = ( + { + "elevated": self.theme_cls.onSurfaceColor[:-1] + + [ + self.button_elevated_opacity_value_disabled_container + ], + "tonal": self.theme_cls.onSurfaceColor[:-1] + + [self.button_tonal_opacity_value_disabled_container], + "filled": self.theme_cls.onSurfaceColor[:-1] + + [self.button_filled_opacity_value_disabled_container], + "outlined": self.theme_cls.onSurfaceColor[:-1] + + [ + self.button_outlined_opacity_value_disabled_container + ], + "text": self.theme_cls.transparentColor, + }[self.style] + if not self.md_bg_color_disabled + else self.md_bg_color_disabled + ) + elif isinstance(self, (MDFabButton, MDExtendedFabButton)): + self.state_layer_color = ( + self.theme_cls.onSurfaceColor[:-1] + + [self.fab_button_opacity_value_disabled_container] + if not self.md_bg_color_disabled + else self.md_bg_color_disabled + ) + elif isinstance(self, MDTextField): + if self.mode == "filled": + self.state_layer_color = self.theme_cls.onSurfaceColor[ + :-1 + ] + [ + self.text_field_filled_opacity_value_disabled_state_container + ] + else: + self.state_layer_color = self.theme_cls.transparentColor + elif isinstance(self.parent, MDSegmentedButtonItem): + self.state_layer_color = ( + self.theme_cls.onSurfaceColor[:-1] + + [self.segmented_button_opacity_value_disabled_container] + if not self.parent.md_bg_color_disabled + else self.parent.md_bg_color_disabled + ) + elif isinstance(self, MDSwitch): + self.state_layer_color = ( + ( + self.theme_cls.surfaceContainerHighestColor + if not self.active + else self.theme_cls.onSurfaceColor + )[:-1] + + [self.switch_opacity_value_disabled_container] + if not self.md_bg_color_disabled + else self.md_bg_color_disabled + ) + elif isinstance(self, BaseListItem): + self.state_layer_color = ( + self.theme_cls.onSurfaceColor[:-1] + + [self.list_opacity_value_disabled_container] + if not self.md_bg_color_disabled + else self.md_bg_color_disabled + ) + elif not value and self._is_already_disabled: + self.state_layer_color = self.theme_cls.transparentColor + self._is_already_disabled = False + + def on_enter(self) -> None: + """Fired when mouse enter the bbox of the widget.""" + + self._state = self.state_hover + self.set_properties_widget() + + def on_leave(self) -> None: + """Fired when the mouse goes outside the widget border.""" + + self._state = 0.0 + self.set_properties_widget() + + def _on_release(self, *args): + """ + Fired when the button is released + (i.e. the touch/click that pressed the button goes away). + """ + + if platform in ["android", "ios"]: + self._state = 0.0 + self.set_properties_widget() + else: + self.on_enter() + + def _on_press(self, *args): + """Fired when the button is pressed.""" + + self._state = self.state_press + self.set_properties_widget() + + def _restore_properties(self): + if self._state == self.state_hover and self.focus_behavior: + if hasattr(self, "elevation_level"): + self._elevation_level = self.elevation_level + if hasattr(self, "shadow_softness"): + self._shadow_softness = self.shadow_softness + if hasattr(self, "md_bg_color"): + self._bg_color = self.md_bg_color + elif not self._state: + if hasattr(self, "elevation_level"): + self.elevation_level = self._elevation_level + if hasattr(self, "shadow_softness"): + self.shadow_softness = self._shadow_softness + if hasattr(self, "bg_color"): + self.bg_color = self._md_bg_color + + # FIXME: For some widgets, the color of the state of its elements is + # ignored. For example, for the `MDSwitch` widget, the color of the status + # of the `Thumb` element and the color of the icon are ignored. + def _get_target_color(self): + from kivymd.uix.card import MDCard + from kivymd.uix.button import ( + MDIconButton, + MDButton, + MDFabButton, + MDExtendedFabButton, + ) + from kivymd.uix.segmentedbutton.segmentedbutton import ( + MDSegmentedButtonContainer, + ) + from kivymd.uix.chip import MDChip + from kivymd.uix.selectioncontrol import MDSwitch, MDCheckbox + from kivymd.uix.list import BaseListItem + from kivymd.uix.textfield import MDTextField + from kivymd.uix.navigationdrawer import MDNavigationDrawerItem + + target_color = None + + if not self.disabled: + self._restore_properties() + + if isinstance(self, MDTextField): + if self.mode == "filled": + target_color = self.theme_cls.onSurfaceColor + else: + target_color = self.theme_cls.transparentColor + elif isinstance(self, (MDCard, BaseListItem)) and not isinstance( + self, MDNavigationDrawerItem + ): + target_color = self.theme_cls.onSurfaceColor + elif isinstance(self, MDNavigationDrawerItem): + target_color = self.theme_cls.onSecondaryContainerColor + elif isinstance(self.parent, MDSegmentedButtonContainer): + target_color = ( + self.theme_cls.onSurfaceColor + if not self.active + else self.theme_cls.onSecondaryContainerColor + ) + elif isinstance(self, MDChip): + # Here, depending on the widget state (focus/pressed...) + # we set the target color of the widget's layer. + # For example: + # + # if self._state == self.state_press: + # target_color = [., ., ., .] + # else: + # ... + if self.type == "assist": + target_color = self.theme_cls.onSurfaceColor + elif self.type in ["filter", "input", "suggestion"]: + target_color = self.theme_cls.onSurfaceVariantColor + elif isinstance(self, MDIconButton): + if self.style == "filled": + target_color = self.theme_cls.onPrimaryColor + elif self.style == "tonal": + target_color = self.theme_cls.onSecondaryContainerColor + elif self.style in ["outlined", "standard"]: + target_color = self.theme_cls.onSurfaceVariantColor + elif isinstance(self, MDButton): + target_color = ( + self.theme_cls.onPrimaryColor + if self.style == "filled" + else self.theme_cls.primaryColor + ) + elif isinstance(self, MDCheckbox): + target_color = ( + self.theme_cls.primaryColor + if self.active + else self.theme_cls.onSurfaceColor + ) + elif isinstance(self, (MDFabButton, MDExtendedFabButton)): + target_color = self.theme_cls.onPrimaryContainerColor + elif isinstance(self, MDSwitch): + target_color = ( + self.theme_cls.primaryColor + if self.active + else self.theme_cls.onSurfaceVariantColor + ) + else: + target_color = self.theme_cls.onSurfaceColor + + return target_color + + def _set_state_layer_color(self): + from kivymd.uix.card import MDCard + from kivymd.uix.button import ( + MDIconButton, + MDButton, + MDFabButton, + MDExtendedFabButton, + ) + from kivymd.uix.segmentedbutton.segmentedbutton import ( + MDSegmentedButtonContainer, + ) + from kivymd.uix.chip import MDChip + from kivymd.uix.selectioncontrol import MDSwitch, MDCheckbox + from kivymd.uix.list import BaseListItem + from kivymd.uix.textfield import MDTextField + from kivymd.uix.tab.tab import MDTabsItemBase + + target_color = self._get_target_color() + if ( + isinstance( + self, + ( + MDCard, + MDTextField, + MDIconButton, + MDButton, + MDFabButton, + MDExtendedFabButton, + MDChip, + MDSwitch, + MDCheckbox, + BaseListItem, + MDTabsItemBase, + ), + ) + or isinstance(self.parent, MDSegmentedButtonContainer) + and target_color + ): + if self._state == self.state_hover and self.focus_behavior: + if ( + not self.focus_color + or self.theme_cls.dynamic_color + and self.theme_focus_color == "Primary" + ): + if ( + isinstance(self, MDTextField) + and self.mode == "outlined" + ): + self.state_layer_color = target_color + else: + self.state_layer_color = target_color[:-1] + [ + self._state + ] + else: + self.state_layer_color = self.focus_color + elif self._state == self.state_press: + self.state_layer_color = target_color[:-1] + [self._state] + elif not self._state: + self.state_layer_color = target_color[:-1] + [self._state] diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/stencil_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/stencil_behavior.py index 4bb5f6d..0c17e74 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/stencil_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/stencil_behavior.py @@ -63,7 +63,7 @@ KivyMD #:import images_path kivymd.images_path - MDCarousel: + Carousel: StencilImage: size_hint: .9, .8 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/toggle_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/toggle_behavior.py index c396a29..df77fed 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/toggle_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/toggle_behavior.py @@ -125,25 +125,16 @@ You can inherit the ``MyToggleButton`` class only from the following classes - :class:`~kivymd.uix.button.MDFillRoundFlatIconButton` """ -__all__ = ("MDToggleButton",) +__all__ = ("MDToggleButtonBehavior",) +from kivy import Logger from kivy.properties import BooleanProperty, ColorProperty from kivy.uix.behaviors import ToggleButtonBehavior -from kivymd.uix.button import ( - ButtonContentsIconText, - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - MDFlatButton, - MDRaisedButton, - MDRectangleFlatButton, - MDRectangleFlatIconButton, - MDRoundFlatButton, - MDRoundFlatIconButton, -) +from kivymd.uix.button import MDButton, MDIconButton, MDFabButton, BaseButton -class MDToggleButton(ToggleButtonBehavior): +class MDToggleButtonBehavior(ToggleButtonBehavior): background_normal = ColorProperty(None) """ Color of the button in ``rgba`` format for the 'normal' state. @@ -180,38 +171,29 @@ class MDToggleButton(ToggleButtonBehavior): def __init__(self, **kwargs): super().__init__(**kwargs) - classinfo = ( - MDRaisedButton, - MDFlatButton, - MDRectangleFlatButton, - MDRectangleFlatIconButton, - MDRoundFlatButton, - MDRoundFlatIconButton, - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - ) + classinfo = (MDButton, MDIconButton, MDFabButton) # Do the object inherited from the "supported" buttons? if not issubclass(self.__class__, classinfo): raise ValueError( f"Class {self.__class__} must be inherited from one of the " f"classes in the list {classinfo}" ) + else: + print(666, self.md_bg_color) + # self.theme_bg_color = "Custom" if ( not self.background_normal ): # This means that if the value == [] or None will return True. # If the object inherits from buttons with background: - if isinstance( - self, - ( - MDRaisedButton, - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - ), - ): + if isinstance(self, BaseButton): + print(111) self.__is_filled = True - self.background_normal = self.theme_cls.primary_color + self.background_normal = ( + "yellow" # self.theme_cls.primary_color + ) # If not background_normal must be the same as the inherited one. else: + print(222) self.background_normal = ( self.md_bg_color[:] if self.md_bg_color else (0, 0, 0, 0) ) @@ -228,25 +210,44 @@ class MDToggleButton(ToggleButtonBehavior): # self.bind(state=self._update_bg) self.fbind("state", self._update_bg) - def _update_bg(self, ins, val): + def _update_bg(self, instance, value): """Updates the color of the background.""" - if val == "down": - self.md_bg_color = self.background_down - if ( - self.__is_filled is False - ): # If the background is transparent, and the button it toggled, - # the font color must be withe [1, 1, 1, 1]. - self.text_color = self.font_color_down + if self.theme_bg_color == "Primary": + self.theme_bg_color = "Custom" + if self.theme_icon_color == "Primary": + self.theme_icon_color = "Custom" + + if value == "down": + if isinstance(self, MDIconButton): + self.md_bg_color = self.theme_cls.primaryColor + self.icon_color = self.theme_cls.onPrimaryColor + + # if ( + # self.__is_filled is False + # ): + # self.text_color = self.font_color_down if self.group: self._release_group(self) else: - self.md_bg_color = self.background_normal - if ( - self.__is_filled is False - ): # If the background is transparent, the font color must be the - # primary color. - self.text_color = self.font_color_normal + if isinstance(self, MDIconButton): + self.md_bg_color = self.theme_cls.surfaceContainerHighestColor + self.icon_color = self.theme_cls.primaryColor - if issubclass(self.__class__, ButtonContentsIconText): - self.icon_color = self.text_color + # if ( + # self.__is_filled is False + # ): + # self.text_color = self.font_color_normal + + # if issubclass(self.__class__, ButtonContentsIconText): + # self.icon_color = self.text_color + + +class MDToggleButton(MDToggleButtonBehavior): + def __init__(self, **kwargs): + super().__init__(**kwargs) + Logger.warning( + f"KivyMD: " + f"The `{self.__class__.__name__}` class has been deprecated. " + f"Use the `MDToggleButtonBehavior` class instead." + ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/touch_behavior.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/touch_behavior.py index 8aca7bf..1f51e68 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/touch_behavior.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/behaviors/touch_behavior.py @@ -16,21 +16,32 @@ Usage .. code-block:: python from kivy.lang import Builder + from kivy.properties import StringProperty from kivymd.app import MDApp from kivymd.uix.behaviors import TouchBehavior - from kivymd.uix.button import MDRaisedButton + from kivymd.uix.button import MDButton KV = ''' - MDScreen: + + style: "elevated" - MyButton: - text: "PRESS ME" + MDButtonText: + text: root.text + + + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + TouchBehaviorButton: + text: "TouchBehavior" pos_hint: {"center_x": .5, "center_y": .5} ''' - class MyButton(MDRaisedButton, TouchBehavior): + class TouchBehaviorButton(MDButton, TouchBehavior): + text = StringProperty() + def on_long_touch(self, *args): print(" event") @@ -41,12 +52,12 @@ Usage print(" event") - class MainApp(MDApp): + class Example(MDApp): def build(self): return Builder.load_string(KV) - MainApp().run() + Example().run() """ __all__ = ("TouchBehavior",) @@ -85,16 +96,18 @@ class TouchBehavior: self.on_triple_tap(touch, *args) def delete_clock(self, widget, touch, *args): + """Removes a key event from `touch.ud`.""" + if self.collide_point(touch.x, touch.y): if "event" in touch.ud: Clock.unschedule(touch.ud["event"]) del touch.ud["event"] def on_long_touch(self, touch, *args): - """Called when the widget is pressed for a long time.""" + """Fired when the widget is pressed for a long time.""" def on_double_tap(self, touch, *args): - """Called by double clicking on the widget.""" + """Fired by double-clicking on the widget.""" def on_triple_tap(self, touch, *args): - """Called by triple clicking on the widget.""" + """Fired by triple clicking on the widget.""" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__init__.py deleted file mode 100644 index d62fda6..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# NOQA F401 -from .bottomnavigation import MDBottomNavigation, MDBottomNavigationItem diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index edb3814..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__pycache__/bottomnavigation.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__pycache__/bottomnavigation.cpython-311.pyc deleted file mode 100644 index 792bc40..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/__pycache__/bottomnavigation.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/bottomnavigation.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/bottomnavigation.kv deleted file mode 100644 index 79f726b..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/bottomnavigation.kv +++ /dev/null @@ -1,118 +0,0 @@ -#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT - - - - orientation: "vertical" - height: - STANDARD_INCREMENT if app.theme_cls.material_style == "M2" else "80dp" - - ScreenManager: - id: tab_manager - transition: root.transition(duration=root.transition_duration) - on_current: - root.dispatch( \ - "on_switch_tabs", \ - root._get_switchig_tab(self.current), \ - self.current \ - ) - - MDBottomNavigationBar: - id: bottom_panel - size_hint_y: None - radius: root.radius - height: - STANDARD_INCREMENT \ - if app.theme_cls.material_style == "M2" else \ - "80dp" - md_bg_color: - root.theme_cls.bg_dark \ - if not root.panel_color \ - else root.panel_color - - MDBoxLayout: - id: tab_bar - pos_hint: {"center_x": .5, "center_y": .5} - size_hint: None, None - height: - STANDARD_INCREMENT \ - if app.theme_cls.material_style == "M2" else \ - "80dp" - - - - md_bg_color: root.panel_color - on_press: self.tab.dispatch("on_tab_press") - on_release: self.tab.dispatch("on_tab_release") - on_touch_down: self.tab.dispatch("on_tab_touch_down", *args) - on_touch_move: self.tab.dispatch("on_tab_touch_move", *args) - on_touch_up: self.tab.dispatch("on_tab_touch_up", *args) - width: - root.panel.width / len(root.panel.ids.tab_manager.screens) \ - if len(root.panel.ids.tab_manager.screens) != 0 \ - else root.panel.width - padding: - 0, "12dp", 0, "12dp" if app.theme_cls.material_style == "M2" else "16dp" - - RelativeLayout: - id: item_container - - MDIcon: - id: _label_icon - icon: root.tab.icon - height: self.height - badge_icon: root.tab.badge_icon - theme_text_color: "Custom" - text_color: root._text_color_normal - opposite_colors: root.opposite_colors - pos: [self.pos[0], self.pos[1]] - font_size: "24dp" - y: item_container.height - self.height - pos_hint: - {"center_x": .5, "center_y": .5} \ - if not root.panel.use_text else \ - {"center_x": .5, "top": 1} - on_icon: - if self.icon not in md_icons.keys(): \ - self.size_hint = (None, None); \ - self.width = self.font_size; \ - self.height = self.font_size - - canvas.before: - Color: - rgba: - ( \ - ( \ - app.theme_cls.disabled_hint_text_color \ - if not root.selected_color_background else \ - root.selected_color_background \ - ) \ - if root.active else \ - (0, 0, 0, 0) \ - ) \ - if app.theme_cls.material_style == "M3" else \ - (0, 0, 0, 0) - RoundedRectangle: - radius: [16,] - size: root._selected_region_width, dp(32) - pos: - self.center_x - root._selected_region_width / 2, \ - self.center_y - (dp(16)) - - MDLabel: - id: _label - text: root.tab.text - size_hint_x: None - text_size: None, root.height - adaptive_height: True - theme_text_color: "Custom" - text_color: root._text_color_normal - opposite_colors: root.opposite_colors - font_size: root._label_font_size - pos_hint: {"center_x": .5} - y: -dp(4) if app.theme_cls.material_style == "M2" else 0 - font_style: - "Button" if app.theme_cls.material_style == "M2" else "Body2" - - - - md_bg_color: root.theme_cls.bg_normal diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/bottomnavigation.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/bottomnavigation.py deleted file mode 100644 index 9b764d4..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomnavigation/bottomnavigation.py +++ /dev/null @@ -1,880 +0,0 @@ -""" -Components/BottomNavigation -=========================== - -.. seealso:: - - `Material Design 2 spec, Bottom navigation `_ and - `Material Design 3 spec, Bottom navigation `_ - -.. rubric:: Bottom navigation bars allow movement between primary destinations in an app: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png - :align: center - -Usage ------ - -.. code-block:: kv - - - - MDBottomNavigation: - - MDBottomNavigationItem: - name: "screen 1" - - YourContent: - - MDBottomNavigationItem: - name: "screen 2" - - YourContent: - - MDBottomNavigationItem: - name: "screen 3" - - YourContent: - -For ease of understanding, this code works like this: - -.. code-block:: kv - - - - ScreenManager: - - Screen: - name: "screen 1" - - YourContent: - - Screen: - name: "screen 2" - - YourContent: - - Screen: - name: "screen 3" - - YourContent: - -Example -------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - - class Test(MDApp): - - def build(self): - self.theme_cls.material_style = "M3" - self.theme_cls.theme_style = "Dark" - return Builder.load_string( - ''' - MDScreen: - - MDBottomNavigation: - #panel_color: "#eeeaea" - selected_color_background: "orange" - text_color_active: "lightgrey" - - MDBottomNavigationItem: - name: 'screen 1' - text: 'Mail' - icon: 'gmail' - badge_icon: "numeric-10" - - MDLabel: - text: 'Mail' - halign: 'center' - - MDBottomNavigationItem: - name: 'screen 2' - text: 'Twitter' - icon: 'twitter' - badge_icon: "numeric-5" - - MDLabel: - text: 'Twitter' - halign: 'center' - - MDBottomNavigationItem: - name: 'screen 3' - text: 'LinkedIN' - icon: 'linkedin' - - MDLabel: - text: 'LinkedIN' - halign: 'center' - ''' - ) - - - Test().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.bottomnavigation import MDBottomNavigation, MDBottomNavigationItem - from kivymd.uix.label import MDLabel - from kivymd.uix.screen import MDScreen - - - class Test(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - self.theme_cls.theme_style = "Dark" - return ( - MDScreen( - MDBottomNavigation( - MDBottomNavigationItem( - MDLabel( - text='Mail', - halign='center', - ), - name='screen 1', - text='Mail', - icon='gmail', - badge_icon="numeric-10", - ), - MDBottomNavigationItem( - MDLabel( - text='Twitter', - halign='center', - ), - name='screen 1', - text='Twitter', - icon='twitter', - badge_icon="numeric-10", - ), - MDBottomNavigationItem( - MDLabel( - text='LinkedIN', - halign='center', - ), - name='screen 1', - text='LinkedIN', - icon='linkedin', - badge_icon="numeric-10", - ), - selected_color_background="orange", - text_color_active="lightgrey", - ) - ) - ) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.gif - :align: center - -.. rubric:: :class:`~MDBottomNavigationItem` provides the following events for use: - -.. code-block:: python - - __events__ = ( - "on_tab_touch_down", - "on_tab_touch_move", - "on_tab_touch_up", - "on_tab_press", - "on_tab_release", - ) - -.. code-block:: kv - - Root: - - MDBottomNavigation: - - MDBottomNavigationItem: - on_tab_touch_down: print("on_tab_touch_down") - on_tab_touch_move: print("on_tab_touch_move") - on_tab_touch_up: print("on_tab_touch_up") - on_tab_press: print("on_tab_press") - on_tab_release: print("on_tab_release") - - YourContent: - -How to automatically switch a tab? ----------------------------------- - -Use method :attr:`~MDBottomNavigation.switch_tab` which takes as argument -the name of the tab you want to switch to. - -Use custom icon ---------------- - -.. code-block:: kv - - MDBottomNavigation: - - MDBottomNavigationItem: - icon: "icon.png" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-custom-icon.png - :align: center -""" - -__all__ = ( - "TabbedPanelBase", - "MDBottomNavigationItem", - "MDBottomNavigation", - "MDTab", -) - -import os -from typing import Union - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.core.window.window_sdl2 import WindowSDL -from kivy.lang import Builder -from kivy.metrics import dp, sp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - StringProperty, -) -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import FadeTransition, ScreenManagerException - -from kivymd import uix_path -from kivymd.material_resources import STANDARD_INCREMENT -from kivymd.theming import ThemableBehavior, ThemeManager -from kivymd.uix.anchorlayout import MDAnchorLayout -from kivymd.uix.behaviors import CommonElevationBehavior, DeclarativeBehavior -from kivymd.uix.behaviors.backgroundcolor_behavior import ( - SpecificBackgroundColorBehavior, -) -from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.screen import MDScreen -from kivymd.utils.set_bars_colors import set_bars_colors - -with open( - os.path.join(uix_path, "bottomnavigation", "bottomnavigation.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDBottomNavigationHeader(ButtonBehavior, MDAnchorLayout): - """ - Bottom navigation header class. - - For more information, see in the - :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.anchorlayout.MDAnchorLayout` - classes documentation. - """ - - panel_color = ColorProperty([1, 1, 1, 0]) - """ - Panel color of bottom navigation in (r, g, b, a) or string format. - - :attr:`panel_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 0]`. - """ - - tab = ObjectProperty() - """ - :attr:`tab` is an :class:`~MDBottomNavigationItem` - and defaults to `None`. - """ - - panel = ObjectProperty() - """ - :attr:`panel` is an :class:`~MDBottomNavigation` - and defaults to `None`. - """ - - active = BooleanProperty(False) - - text = StringProperty() - """ - :attr:`text` is an :class:`~MDTab.text` - and defaults to `''`. - """ - - text_color_normal = ColorProperty([1, 1, 1, 1]) - """ - Text color in (r, g, b, a) or string format of the label when it is not - selected. - - :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - text_color_active = ColorProperty([1, 1, 1, 1]) - """ - Text color in (r, g, b, a) or string format of the label when it is selected. - - :attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - selected_color_background = ColorProperty(None) - """ - The background color in (r, g, b, a) or string format of the highlighted - item when using Material Design v3. - - .. versionadded:: 1.0.0 - - :attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - opposite_colors = BooleanProperty(True) - - _label = ObjectProperty() - _label_font_size = NumericProperty("12sp") - _text_color_normal = ColorProperty([1, 1, 1, 1]) - _text_color_active = ColorProperty([1, 1, 1, 1]) - _selected_region_width = NumericProperty(dp(64)) - - def __init__(self, panel, tab): - self.panel = panel - self.tab = tab - super().__init__() - self._text_color_normal = ( - self.theme_cls.disabled_hint_text_color - if self.text_color_normal == [1, 1, 1, 1] - else self.text_color_normal - ) - self._label = self.ids._label - self._label_font_size = sp(12) - self.theme_cls.bind(disabled_hint_text_color=self._update_theme_style) - self.active = False - - def on_press(self) -> None: - """Called when clicking on a panel item.""" - - if self.theme_cls.material_style == "M2": - Animation(_label_font_size=sp(14), d=0.1).start(self) - elif self.theme_cls.material_style == "M3": - Animation( - _selected_region_width=dp(64), - t="in_out_sine", - d=0, - ).start(self) - Animation( - _text_color_normal=self.theme_cls.primary_color - if self.text_color_active == [1, 1, 1, 1] - else self.text_color_active, - d=0.1, - ).start(self) - - def _update_theme_style( - self, instance_theme_manager: ThemeManager, color: list - ): - """Called when the application theme style changes (White/Black).""" - - if not self.active: - self._text_color_normal = ( - color - if self.text_color_normal == [1, 1, 1, 1] - else self.text_color_normal - ) - - -class MDTab(MDScreen): - """ - A tab is simply a screen with meta information that defines the content - that goes in the tab header. - - For more information, see in the - :class:`~kivymd.uix.screen.MDScreen` class documentation. - """ - - __events__ = ( - "on_tab_touch_down", - "on_tab_touch_move", - "on_tab_touch_up", - "on_tab_press", - "on_tab_release", - ) - """Events provided.""" - - text = StringProperty() - """ - Tab header text. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon = StringProperty("checkbox-blank-circle") - """ - Tab header icon. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle'`. - """ - - badge_icon = StringProperty() - """ - Tab header badge icon. - - .. versionadded:: 1.0.0 - - :attr:`badge_icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.index = 0 - self.parent_widget = None - self.register_event_type("on_tab_touch_down") - self.register_event_type("on_tab_touch_move") - self.register_event_type("on_tab_touch_up") - self.register_event_type("on_tab_press") - self.register_event_type("on_tab_release") - - def on_tab_touch_down(self, *args): - pass - - def on_tab_touch_move(self, *args): - pass - - def on_tab_touch_up(self, *args): - pass - - def on_tab_press(self, *args): - par = self.parent_widget - if par.previous_tab is not self: - if par.previous_tab.index > self.index: - par.ids.tab_manager.transition.direction = "right" - elif par.previous_tab.index < self.index: - par.ids.tab_manager.transition.direction = "left" - par.ids.tab_manager.current = self.name - par.previous_tab = self - - def on_tab_release(self, *args): - pass - - def __repr__(self): - return f"" - - -class MDBottomNavigationItem(MDTab): - header = ObjectProperty() - """ - :attr:`header` is an :class:`~MDBottomNavigationHeader` - and defaults to `None`. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def animate_header( - self, bottom_navigation_object, bottom_navigation_header_object - ) -> None: - if bottom_navigation_object.use_text: - Animation(_label_font_size=sp(12), d=0.1).start( - bottom_navigation_object.previous_tab.header - ) - Animation( - _selected_region_width=0, - t="in_out_sine", - d=0, - ).start(bottom_navigation_header_object) - Animation( - _text_color_normal=bottom_navigation_header_object.text_color_normal - if bottom_navigation_object.previous_tab.header.text_color_normal - != [1, 1, 1, 1] - else self.theme_cls.disabled_hint_text_color, - d=0.1, - ).start(bottom_navigation_object.previous_tab.header) - bottom_navigation_object.previous_tab.header.active = False - self.header.active = True - - def on_tab_press(self, *args) -> None: - """Called when clicking on a panel item.""" - - bottom_navigation_object = self.parent_widget - bottom_navigation_header_object = ( - bottom_navigation_object.previous_tab.header - ) - - if bottom_navigation_object.previous_tab is not self: - self.animate_header( - bottom_navigation_object, bottom_navigation_header_object - ) - - super().on_tab_press(*args) - - def on_disabled( - self, instance_bottom_navigation_item, disabled_value: bool - ) -> None: - self.header.disabled = disabled_value - - def on_leave(self, *args): - pass - - -class TabbedPanelBase( - ThemableBehavior, SpecificBackgroundColorBehavior, BoxLayout -): - """ - A class that contains all variables a :class:`~kivy.properties.TabPannel` - must have. It is here so I (zingballyhoo) don't get mad about - the :class:`~kivy.properties.TabbedPannels` not being DRY. - - For more information, see in the :class:`~kivymd.theming.ThemableBehavior` - and :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior` - and :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation. - """ - - current = StringProperty(None) - """ - Current tab name. - - :attr:`current` is an :class:`~kivy.properties.StringProperty` - and defaults to `None`. - """ - - previous_tab = ObjectProperty(None, aloownone=True) - """ - :attr:`previous_tab` is an :class:`~MDTab` and defaults to `None`. - """ - - panel_color = ColorProperty(None) - """ - Panel color of bottom navigation. - - :attr:`panel_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tabs = ListProperty() - - -class MDBottomNavigation(DeclarativeBehavior, TabbedPanelBase): - """ - A bottom navigation that is implemented by delegating all items to a - :class:`~kivy.uix.screenmanager.ScreenManager`. - - For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~TabbedPanelBase` classes documentation. - - :Events: - :attr:`on_switch_tabs` - Called when switching tabs. Returns the object of the tab to be - opened. - - .. versionadded:: 1.0.0 - """ - - transition = ObjectProperty(FadeTransition) - """ - Transition animation of bottom navigation screen manager. - - .. versionadded:: 1.1.0 - - :attr:`transition` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `FadeTransition`. - """ - - transition_duration = NumericProperty(0.2) - """ - Duration animation of bottom navigation screen manager. - - .. versionadded:: 1.1.0 - - :attr:`transition_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - text_color_normal = ColorProperty([1, 1, 1, 1]) - """ - Text color of the label when it is not selected. - - .. code-block:: kv - - MDBottomNavigation: - text_color_normal: 1, 0, 1, 1 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_normal.png - - :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - text_color_active = ColorProperty([1, 1, 1, 1]) - """ - Text color of the label when it is selected. - - .. code-block:: kv - - MDBottomNavigation: - text_color_active: 0, 0, 0, 1 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-text_color_active.png - - :attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - use_text = BooleanProperty(True) - """ - Use text for :class:`~MDBottomNavigationItem` or not. - If ``True``, the :class:`~MDBottomNavigation` panel height will be reduced - by the text height. - - .. versionadded:: 1.0.0 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-use-text.png - :align: center - - :attr:`use_text` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - selected_color_background = ColorProperty(None) - """ - The background color of the highlighted item when using Material Design v3. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDBottomNavigation: - selected_color_background: 0, 0, 1, .4 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation=selected-color-background.png - - :attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_name = StringProperty("Roboto") - """ - Font name of the label. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDBottomNavigation: - font_name: "path/to/font.ttf" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation-font-name.png - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - first_widget = ObjectProperty() - """ - :attr:`first_widget` is an :class:`~MDBottomNavigationItem` - and defaults to `None`. - """ - - tab_header = ObjectProperty() - """ - :attr:`tab_header` is an :class:`~MDBottomNavigationHeader` - and defaults to `None`. - """ - - set_bars_color = BooleanProperty(False) - """ - If `True` the background color of the navigation bar will be set - automatically according to the current color of the toolbar. - - .. versionadded:: 1.0.0 - - :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - widget_index = NumericProperty(0) - - # Text active color if it is selected. - _active_color = ColorProperty([1, 1, 1, 1]) - - def __init__(self, *args, **kwargs): - self.previous_tab = None - self.register_event_type("on_switch_tabs") - super().__init__(*args, **kwargs) - self.theme_cls.bind(material_style=self.refresh_tabs) - Window.bind(on_resize=self.on_resize) - Clock.schedule_once(lambda x: self.on_resize()) - Clock.schedule_once(self.set_status_bar_color) - - def set_status_bar_color(self, interval: Union[int, float]) -> None: - if self.set_bars_color: - set_bars_colors(self.panel_color, None, self.theme_cls.theme_style) - - def switch_tab(self, name_tab) -> None: - """Switching the tab by name.""" - - if not self.ids.tab_manager.has_screen(name_tab): - raise ScreenManagerException(f"No Screen with name '{name_tab}'.") - self.ids.tab_manager.get_screen(name_tab).dispatch("on_tab_press") - count_index_screen = [ - self.ids.tab_manager.screens.index(screen) - for screen in self.ids.tab_manager.screens - if screen.name == name_tab - ][0] - numbers_screens = list(range(len(self.ids.tab_manager.screens))) - numbers_screens.reverse() - self.ids.tab_bar.children[ - numbers_screens.index(count_index_screen) - ].dispatch("on_press") - - def refresh_tabs(self, *args) -> None: - """Refresh all tabs.""" - - if self.ids: - tab_bar = self.ids.tab_bar - tab_bar.clear_widgets() - tab_manager = self.ids.tab_manager - self._active_color = self.theme_cls.primary_color - - if self.text_color_active != [1, 1, 1, 1]: - self._active_color = self.text_color_active - - for tab in tab_manager.screens: - self.tab_header = MDBottomNavigationHeader(tab=tab, panel=self) - tab.header = self.tab_header - tab_bar.add_widget(self.tab_header) - - if tab is self.first_widget: - self.tab_header._text_color_normal = self._active_color - self.tab_header._label_font_size = sp(14) - self.tab_header.active = True - else: - self.tab_header.ids._label.font_size = sp(12) - self.tab_header._label_font_size = sp(12) - - def on_font_name(self, instance_bottom_navigation, font_name: str) -> None: - for tab in self.ids.tab_bar.children: - tab.ids._label.font_name = font_name - - def on_selected_color_background( - self, instance_bottom_navigation, color: list - ) -> None: - def on_selected_color_background(*args): - for tab in self.ids.tab_bar.children: - tab.selected_color_background = color - - Clock.schedule_once(on_selected_color_background) - - def on_use_text( - self, instance_bottom_navigation, use_text_value: bool - ) -> None: - if not use_text_value: - for instance_bottom_navigation_header in self.ids.tab_bar.children: - instance_bottom_navigation_header.ids.item_container.remove_widget( - instance_bottom_navigation_header.ids._label - ) - if self.theme_cls.material_style == "M2": - height = dp(42) - else: - height = dp(80) - self.height = height - self.ids.bottom_panel.height = height - self.ids.tab_bar.height = height - else: - if self.theme_cls.material_style == "M2": - height = STANDARD_INCREMENT - else: - height = dp(80) - self.height = height - self.ids.bottom_panel.height = height - self.ids.tab_bar.height = height - - def on_text_color_normal( - self, instance_bottom_navigation, color: list - ) -> None: - MDBottomNavigationHeader.text_color_normal = color - for tab in self.ids.tab_bar.children: - if not tab.active: - tab._text_color_normal = color - - def on_text_color_active( - self, instance_bottom_navigation, color: list - ) -> None: - def on_text_color_active(*args): - MDBottomNavigationHeader.text_color_active = color - self.text_color_active = color - for tab in self.ids.tab_bar.children: - tab.text_color_active = color - if tab.active: - tab._text_color_normal = color - - Clock.schedule_once(on_text_color_active) - - def on_switch_tabs(self, bottom_navigation_item, name_tab: str) -> None: - """ - Called when switching tabs. Returns the object of the tab to be opened. - """ - - def on_size(self, *args) -> None: - self.on_resize() - - def on_resize( - self, - instance: Union[WindowSDL, None] = None, - width: Union[int, None] = None, - do_again: bool = True, - ) -> None: - """Called when the application window is resized.""" - - full_width = 0 - for tab in self.ids.tab_manager.screens: - full_width += tab.header.width - tab.header.text_color_normal = self.text_color_normal - self.ids.tab_bar.width = full_width - if do_again: - Clock.schedule_once(lambda x: self.on_resize(do_again=False), 0.1) - - def add_widget(self, widget, **kwargs): - if isinstance(widget, MDBottomNavigationItem): - self.widget_index += 1 - widget.index = self.widget_index - widget.parent_widget = self - self.ids.tab_manager.add_widget(widget) - if self.widget_index == 1: - self.previous_tab = widget - self.first_widget = widget - self.refresh_tabs() - else: - super().add_widget(widget) - - def remove_widget(self, widget): - if isinstance(widget, MDBottomNavigationItem): - self.ids.tab_manager.remove_widget(widget) - self.refresh_tabs() - else: - super().remove_widget(widget) - - def _get_switchig_tab(self, name_tab: str) -> MDBottomNavigationItem: - bottom_navigation_item = None - for bottom_navigation_header_instance in self.ids.tab_bar.children: - if bottom_navigation_header_instance.tab.name == name_tab: - bottom_navigation_item = bottom_navigation_header_instance.tab - break - return bottom_navigation_item - - -class MDBottomNavigationBar(CommonElevationBehavior, MDFloatLayout): - pass diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__init__.py index dec1c23..6f49d65 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__init__.py @@ -1,11 +1,7 @@ # NOQA F401 from .bottomsheet import ( MDBottomSheet, - MDBottomSheetContent, MDBottomSheetDragHandle, MDBottomSheetDragHandleButton, MDBottomSheetDragHandleTitle, - MDCustomBottomSheet, - MDGridBottomSheet, - MDListBottomSheet, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/__init__.cpython-311.pyc index 5b98c82..7f3e9ac 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/bottomsheet.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/bottomsheet.cpython-311.pyc index efd4718..4235cc7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/bottomsheet.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/__pycache__/bottomsheet.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.kv index 6d63e1c..1b22bfd 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.kv @@ -1,6 +1,4 @@ - - size_hint_y: None - height: self.minimum_height +#:import Window kivy.core.window.Window @@ -10,13 +8,19 @@ padding: "16dp", "8dp", "16dp", "16dp" BottomSheetDragHandle: - md_bg_color: - app.theme_cls.disabled_hint_text_color \ - if not root.drag_handle_color else \ - root.drag_handle_color + canvas: + Color: + rgba: + app.theme_cls.disabled_hint_text_color \ + if not root.drag_handle_color else \ + root.drag_handle_color + SmoothRoundedRectangle: + pos: self.pos + size: self.size + radius: [dp(4), ] + size_hint: None, None size: "32dp", "4dp" - radius: 4 pos_hint: {"center_x": .5} BottomSheetDragHandleContainer: @@ -27,16 +31,14 @@ orientation: "vertical" - md_bg_color: root.bg_color if root.bg_color else app.theme_cls.bg_darkest - radius: 16, 16, 0, 0 + radius: "16dp", "16dp", 0, 0 padding: 0, "8dp", 0, 0 + -x: 0 + width: Window.width if Window.width <= dp(640) else dp(640) + pos_hint: {"center_x": .5} + y: self.height * (self.open_progress - 1) - MDBoxLayout: + BoxLayout: id: drag_handle_container size_hint_y: None height: self.minimum_height - - MDBoxLayout: - id: container - size_hint_y: None - height: self.minimum_height \ No newline at end of file diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.py index 4961ff0..afe6a66 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/bottomsheet/bottomsheet.py @@ -16,11 +16,16 @@ Usage .. code-block:: kv - MDScreen: + Root: - [ Content screen ] + MDNavigationLayout: + + MDScreenManager: + + [...] + + MDBottomSheet: - MDBottomSheet: The bottom sheet has two types: @@ -28,6 +33,7 @@ The bottom sheet has two types: - Modal_ .. Standard: + Standard -------- @@ -39,150 +45,14 @@ frequently scrolled or panned. Use a standard bottom sheet to display content that complements the screen’s primary content, such as an audio player in a music app. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-standard.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-standard.gif :align: center Standard bottom sheets are elevated above the main UI region so their visibility is not affected by panning or scrolling. -Standard bottom sheet example ------------------------------ - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDBoxLayout: - orientation: "vertical" - padding: "12dp" - adaptive_height: True - pos_hint: {"top": 1} - - MDSmartTile: - id: smart_tile - source: "https://picsum.photos/id/70/3011/2000" - radius: 16 - box_radius: [0, 0, 16, 16] - size_hint_y: None - height: "240dp" - on_release: - bottom_sheet.open() \\ - if bottom_sheet.state == "close" else \\ - bottom_sheet.dismiss() - - MDLabel: - bold: True - color: 1, 1, 1, 1 - text: - "Tap to open the bottom sheet" \\ - if bottom_sheet.state == "close" else \\ - "Tap to close the bottom sheet" - - MDBottomSheet: - id: bottom_sheet - type: "standard" - bg_color: "grey" - default_opening_height: smart_tile.y - dp(12) - size_hint_y: None - height: root.height - (smart_tile.height + dp(24)) - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivy.clock import Clock - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.bottomsheet import MDBottomSheet - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.imagelist import MDSmartTile - from kivymd.uix.label import MDLabel - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return MDScreen( - MDBoxLayout( - MDSmartTile( - MDLabel( - id="tile_label", - text="Tap to open the bottom sheet", - bold=True, - color=(1, 1, 1, 1), - ), - id="smart_tile", - source="https://picsum.photos/id/70/3011/2000", - radius=16, - box_radius=[0, 0, 16, 16], - size_hint_y=None, - height="240dp", - ), - id="box", - orientation="vertical", - padding="12dp", - pos_hint={"top": 1}, - adaptive_height=True, - ), - MDBottomSheet( - id="bottom_sheet", - size_hint_y=None, - type="standard", - bg_color="grey", - ), - ) - - def open_bottom_sheet(self, *args): - bottom_sheet = self.root.ids.bottom_sheet - smart_tile = self.root.ids.box.ids.smart_tile - tile_label = smart_tile.ids.tile_label - bottom_sheet.open() if bottom_sheet.state == "close" else bottom_sheet.dismiss() - tile_label.text = ( - "Tap to open the bottom sheet" - if bottom_sheet.state == "close" - else "Tap to close the bottom sheet" - ) - - def on_start(self): - def on_start(*args): - bottom_sheet = self.root.ids.bottom_sheet - smart_tile = self.root.ids.box.ids.smart_tile - bottom_sheet.default_opening_height = smart_tile.y - dp(12) - bottom_sheet.height = self.root.height - ( - smart_tile.height + dp(24) - ) - smart_tile.bind(on_release=lambda x: self.open_bottom_sheet()) - - Clock.schedule_once(on_start, 1.2) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-standard-example.gif - :align: center - .. Modal: + Modal ----- @@ -191,129 +61,7 @@ appear in front of app content, disabling all other app functionality when they appear, and remaining on screen until confirmed, dismissed, or a required action has been taken. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal.png - :align: center - -Modal bottom sheet example --------------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDBoxLayout: - orientation: "vertical" - padding: "12dp" - adaptive_height: True - pos_hint: {"top": 1} - - MDSmartTile: - id: smart_tile - source: "https://picsum.photos/id/70/3011/2000" - radius: 16 - box_radius: [0, 0, 16, 16] - size_hint_y: None - height: "240dp" - on_release: bottom_sheet.open() - - MDLabel: - bold: True - color: 1, 1, 1, 1 - text: "Tap to open the modal bottom sheet" - - MDBottomSheet: - id: bottom_sheet - bg_color: "grey" - default_opening_height: smart_tile.y - dp(12) - size_hint_y: None - height: root.height - (smart_tile.height + dp(24)) - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivy.clock import Clock - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.bottomsheet import MDBottomSheet - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.imagelist import MDSmartTile - from kivymd.uix.label import MDLabel - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return MDScreen( - MDBoxLayout( - MDSmartTile( - MDLabel( - id="tile_label", - text="Tap to open the modal bottom sheet", - bold=True, - color=(1, 1, 1, 1), - ), - id="smart_tile", - source="https://picsum.photos/id/70/3011/2000", - radius=16, - box_radius=[0, 0, 16, 16], - size_hint_y=None, - height="240dp", - ), - id="box", - orientation="vertical", - padding="12dp", - pos_hint={"top": 1}, - adaptive_height=True, - ), - MDBottomSheet( - id="bottom_sheet", - size_hint_y=None, - bg_color="grey", - ), - ) - - def open_bottom_sheet(self, *args): - bottom_sheet = self.root.ids.bottom_sheet - bottom_sheet.open() - - def on_start(self): - def on_start(*args): - bottom_sheet = self.root.ids.bottom_sheet - smart_tile = self.root.ids.box.ids.smart_tile - bottom_sheet.default_opening_height = smart_tile.y - dp(12) - bottom_sheet.height = self.root.height - ( - smart_tile.height + dp(24) - ) - smart_tile.bind(on_release=lambda x: self.open_bottom_sheet()) - - Clock.schedule_once(on_start, 1.2) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal-example.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal.gif :align: center Tapping the scrim dismisses a modal bottom sheet. @@ -321,41 +69,6 @@ Tapping the scrim dismisses a modal bottom sheet. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-modal-tapping.png :align: center -Custom positioning ------------------- - -The optional drag handle provides an affordance for custom sheet height, -or for a quick toggle through preset heights. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle.png - :align: center - -.. code-block:: kv - - MDBottomSheet: - - MDBottomSheetDragHandle: - -By default, when you drag and then release the drag handle, the bottom sheet -will be closed or expand to the full screen, depending on whether you released -the drag handle closer to the top or to the bottom of the screen: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle.gif - :align: center - -In order to manually adjust the height of the bottom sheet with the drag handle, -set the `auto_positioning` parameter to `False`: - -.. code-block:: kv - - MDBottomSheet: - auto_positioning: False - - MDBottomSheetDragHandle: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle-auto-positioning.gif - :align: center - Add elements to :class:`~MDBottomSheetDragHandleTitle` class ------------------------------------------------------------ @@ -368,7 +81,6 @@ Add elements to :class:`~MDBottomSheetDragHandleTitle` class MDBottomSheetDragHandleTitle: text: "MDBottomSheet" adaptive_height: True - font_style: "H6" pos_hint: {"center_y": .5} MDBottomSheetDragHandleButton: @@ -377,36 +89,6 @@ Add elements to :class:`~MDBottomSheetDragHandleTitle` class .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-drag-handle-elements.png :align: center -Add custom content to :class:`~MDBottomSheet` class ---------------------------------------------------- - -To add custom content to the bottom sheet, use the -:class:`~MDBottomSheetContent` class: - -.. code-block:: kv - - MDBottomSheet: - bg_color: "darkgrey" - type: "standard" - max_opening_height: self.height - default_opening_height: self.max_opening_height - adaptive_height: True - - MDBottomSheetDragHandle: - drag_handle_color: "grey" - - MDBottomSheetContent: - padding: "16dp" - - MDLabel: - text: "Content" - halign: "center" - font_style: "H5" - adaptive_height: True - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-content.png - :align: center - A practical example with standard bottom sheet ---------------------------------------------- @@ -414,6 +96,8 @@ A practical example with standard bottom sheet .. code-block:: python + import asynckivy + from kivy.lang import Builder from kivy.properties import StringProperty, ObjectProperty, BooleanProperty from kivy_garden.mapview import MapView @@ -421,11 +105,10 @@ A practical example with standard bottom sheet from kivymd.app import MDApp from kivymd.uix.behaviors import TouchBehavior from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.utils import asynckivy KV = ''' #:import MapSource kivy_garden.mapview.MapSource - #:import asynckivy kivymd.utils.asynckivy + #:import asynckivy asynckivy @@ -436,14 +119,14 @@ A practical example with standard bottom sheet MDIconButton: id: icon icon: root.icon - md_bg_color: "#EDF1F9" if not root.selected else app.theme_cls.primary_color + theme_bg_color: "Custom" + md_bg_color: "#EDF1F9" if not root.selected else app.theme_cls.primaryColor pos_hint: {"center_x": .5} theme_icon_color: "Custom" icon_color: "white" if root.selected else "black" on_release: app.set_active_element(root, root.title.lower()) MDLabel: - font_size: "14sp" text: root.title pos_hint: {"center_x": .5} halign: "center" @@ -452,41 +135,41 @@ A practical example with standard bottom sheet MDScreen: - CustomMapView: - bottom_sheet: bottom_sheet - map_source: MapSource(url=app.map_sources[app.current_map]) - lat: 46.5124 - lon: 47.9812 - zoom: 12 + MDNavigationLayout: - MDBottomSheet: - id: bottom_sheet - elevation: 2 - shadow_softness: 6 - bg_color: "white" - type: "standard" - max_opening_height: self.height - default_opening_height: self.max_opening_height - adaptive_height: True - on_open: asynckivy.start(app.generate_content()) + MDScreenManager: - MDBottomSheetDragHandle: - drag_handle_color: "grey" + MDScreen: - MDBottomSheetDragHandleTitle: - text: "Select type map" - adaptive_height: True - bold: True - pos_hint: {"center_y": .5} + CustomMapView: + bottom_sheet: bottom_sheet + map_source: MapSource(url=app.map_sources[app.current_map]) + lat: 46.5124 + lon: 47.9812 + zoom: 12 - MDBottomSheetDragHandleButton: - icon: "close" - _no_ripple_effect: True - on_release: bottom_sheet.dismiss() + MDBottomSheet: + id: bottom_sheet + sheet_type: "standard" + size_hint_y: None + height: "150dp" + on_open: asynckivy.start(app.generate_content()) - MDBottomSheetContent: - id: content_container - padding: 0, 0, 0, "16dp" + MDBottomSheetDragHandle: + drag_handle_color: "grey" + + MDBottomSheetDragHandleTitle: + text: "Select type map" + pos_hint: {"center_y": .5} + + MDBottomSheetDragHandleButton: + icon: "close" + ripple_effect: False + on_release: bottom_sheet.set_state("toggle") + + BoxLayout: + id: content_container + padding: 0, 0, 0, "16dp" ''' @@ -501,7 +184,7 @@ A practical example with standard bottom sheet def on_double_tap(self, touch, *args): if self.bottom_sheet: - self.bottom_sheet.open() + self.bottom_sheet.set_state("toggle") class Example(MDApp): @@ -543,17 +226,62 @@ A practical example with standard bottom sheet Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-sheet-real-example.gif - :align: center +API break +========= +1.2.0 version +------------- + +.. code-block:: kv + + Root: + + MDBottomSheet: + + # Optional. + MDBottomSheetDragHandle: + + # Optional. + MDBottomSheetDragHandleTitle: + + # Optional. + MDBottomSheetDragHandleButton: + + MDBottomSheetContent: + [...] + +2.0.0 version +------------- + +.. code-block:: kv + + Root: + + MDNavigationLayout: + + MDScreenManager: + + # Your screen. + MDScreen: + + MDBottomSheet: + + # Optional. + MDBottomSheetDragHandle: + + # Optional. + MDBottomSheetDragHandleTitle: + + # Optional. + MDBottomSheetDragHandleButton: + icon: "close" + + # Your content. + BoxLayout: """ __all__ = ( - "MDCustomBottomSheet", - "MDGridBottomSheet", - "MDListBottomSheet", "MDBottomSheet", - "MDBottomSheetContent", "MDBottomSheetDragHandle", "MDBottomSheetDragHandleTitle", "MDBottomSheetDragHandleButton", @@ -561,71 +289,38 @@ __all__ = ( import os -from kivy import Logger -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.screenmanager import Screen +from kivy.properties import ColorProperty, OptionProperty +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.widget import Widget from kivymd import uix_path -from kivymd.uix.behaviors import CommonElevationBehavior, TouchBehavior -from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.button import MDIconButton from kivymd.uix.label import MDLabel -from kivymd.uix.screen import MDScreen -from kivymd.uix.widget import MDWidget +from kivymd.uix.navigationdrawer import MDNavigationDrawer with open( os.path.join(uix_path, "bottomsheet", "bottomsheet.kv"), encoding="utf-8", ) as kv_file: - Builder.load_string(kv_file.read()) + Builder.load_string(kv_file.read(), filename="MDBottomSheet.kv") -class BottomSheetDragHandle(MDWidget): +class BottomSheetDragHandle(Widget): pass -class BottomSheetDragHandleContainer(MDBoxLayout): +class BottomSheetDragHandleContainer(BoxLayout): pass -class BottomSheetScrimLayer(MDWidget): - """ - Implements a transparency layer to shade the parent widget - on which the bottom sheet is displayed. - """ - - -class MDBottomSheetContent(MDBoxLayout): - """ - Implements a container for custom content for the :class:`~MDBottomSheet` - class - - For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. - - .. versionadded:: 1.2.0 - """ - - class MDBottomSheetDragHandleButton(MDIconButton): """ Implements a close button (or other functionality) for the :class:`~MDBottomSheetDragHandle` container. For more information, see in the - :class:`~kivymd.uix.button.MDIconButton` class documentation. + :class:`~kivymd.uix.button.button.MDIconButton` class documentation. .. versionadded:: 1.2.0 """ @@ -636,20 +331,20 @@ class MDBottomSheetDragHandleTitle(MDLabel): Implements a header for the :class:`~MDBottomSheetDragHandle` container. For more information, see in the - :class:`~kivymd.uix.label.MDLabel` class documentation. + :class:`~kivymd.uix.label.label.MDLabel` class documentation. .. versionadded:: 1.2.0 """ -class MDBottomSheetDragHandle(MDBoxLayout): +class MDBottomSheetDragHandle(BoxLayout): """ Implements a container that can place the header of the bottom sheet and the close button. Also implements the event of dragging the bottom sheet on the parent screen. For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. .. versionadded:: 1.2.0 """ @@ -685,479 +380,69 @@ class MDBottomSheetDragHandle(MDBoxLayout): return super().add_widget(widget) -class MDBottomSheet(MDBoxLayout, CommonElevationBehavior, TouchBehavior): +class MDBottomSheet(MDNavigationDrawer): """ Bottom sheet class. For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` and - :class:`~kivymd.uix.behaviors.touch_behavior.CommonElevationBehavior` and - :class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior` - classes documentation. - - :Events: - `on_open` - Event when opening the bottom sheet. - `on_close` - Event when closing the bottom sheet. - `on_progress` - Bottom sheet opening/closing progress event. + :class:`~kivymd.uix.navigationdrawer.navigationdrawer.MDNavigationDrawer` + class documentation. """ - auto_dismiss = BooleanProperty(True) + sheet_type = OptionProperty("modal", options=("standard", "modal")) """ - This property determines if the view is automatically - dismissed when the user clicks outside it. + Type of sheet. - .. versionadded:: 1.2.0 + Standard bottom sheets co-exist with the screen’s main UI region and allow + for simultaneously viewing and interacting with both regions, especially + when the main UI region is frequently scrolled or panned. Use a standard + bottom sheet to display content that complements the screen’s primary + content, such as an audio player in a music app. - :attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. + Like dialogs, modal bottom sheets appear in front of app content, + disabling all other app functionality when they appear, and remaining on + screen until confirmed, dismissed, or a required action has been taken. + + .. versionchanged:: 2.0.0 + + Rename from `type` to `sheet_type`. + + :attr:`sheet_type` is a :class:`~kivy.properties.OptionProperty` + and defaults to `'modal'`. """ - type = OptionProperty("modal", options=["modal", "standard"]) - """ - Type sheet. There are two types of bottom sheets: standard and modal. - Available options are: `'modal'`, `'standard'`. + def on_sheet_type(self, instance, value) -> None: + """Fired when the :attr:`sheet_type` value changes.""" - .. versionadded:: 1.2.0 - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'modal`. - """ - - auto_positioning = BooleanProperty(True) - """ - Close or expand the bottom menu automatically when you release the - drag handle. - - .. versionadded:: 1.2.0 - - :attr:`auto_positioning` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - max_opening_height = NumericProperty(None, allownone=True) - """ - The maximum height a that the bottom sheet can be opened using the - drag handle. - - .. versionadded:: 1.2.0 - - .. code-block:: kv - - MDBottomSheet: - max_opening_height: "300dp" - - MDBottomSheetDragHandle: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-max-opening-height.gif - :align: center - - :attr:`max_opening_height` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `None`. - """ - - opening_transition = StringProperty("out_cubic") - """ - The name of the animation transition type to use when animating to - the :attr:`state` `'open'`. - - .. versionadded:: 1.2.0 - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - closing_transition = StringProperty("out_sine") - """The name of the animation transition type to use when animating to - the :attr:`state` 'close'. - - .. versionadded:: 1.2.0 - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_sine'`. - """ - - default_opening_height = NumericProperty(dp(200)) - """ - Default opening height of the bottom sheet. - - .. versionadded:: 1.2.0 - - :attr:`default_opening_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(100)`. - """ - - duration_opening = NumericProperty(0.15) - """ - The duration of the bottom sheet opening animation. - - :attr:`duration_opening` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.15`. - """ - - duration_closing = NumericProperty(0.15) - """ - The duration of the bottom sheet dialog closing animation. - - :attr:`duration_closing` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.15`. - """ - - animation = BooleanProperty(True) - """ - Whether to use animation for opening and closing of the bottom sheet - or not. - - :attr:`animation` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - state = OptionProperty("close", options=["close", "open"]) - """ - Menu state. Available options are: `'close'`, `'open'`. - - .. versionadded:: 1.2.0 - - :attr:`state` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - scrim_layer_color = ColorProperty([0, 0, 0, 1]) - """ - Color for scrim in (r, g, b, a) or string format. - - .. versionadded:: 1.2.0 - - :attr:`scrim_layer_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 1]`. - """ - - bg_color = ColorProperty(None) - """ - Background color of bottom sheet in (r, g, b, a) or string format. - - :attr:`bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - radius_from = OptionProperty( - None, - options=[ - "top_left", - "top_right", - "top", - "bottom_right", - "bottom_left", - "bottom", - ], - allownone=True, - deprecated=True, - ) - """ - Sets which corners to cut from the dialog. Available options are: - `"top_left"`, `"top_right"`, `"top"`, `"bottom_right"`, `"bottom_left"`, - `"bottom"`. - - .. deprecated:: 1.2.0 - Use :attr:`radius` instead. - - :attr:`radius_from` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - value_transparent = ColorProperty([0, 0, 0, 0.8], deprecated=True) - """ - Background color in (r, g, b, a) or string format transparency value when - opening a dialog. - - .. deprecated:: 1.2.0 - - :attr:`value_transparent` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.8]`. - """ - - _diff_between_touch_height_sheet = 0 - _alpha_channel_value = 0 - # Menu state: - # - value 'down' - menu is captured; - # - value 'none' - menu is not captured; - _state = OptionProperty("none", options=["none", "down"]) - # There was a touch to the bottom sheet. - _touch_sheet = False - # kivymd.uix.bottomsheet.bottomsheet.BottomSheetScrimLayer object. - _scrim_layer = ObjectProperty(None, allownone=True) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.y = -Window.height # start bottom sheet position - Clock.schedule_once(self.check_parent) - Clock.schedule_once(self.check_max_opening_height) - Clock.schedule_once(self.add_scrim_layer) - self.register_event_type("on_open") - self.register_event_type("on_close") - self.register_event_type("on_progress") - - def on_progress(self, *args) -> None: - """Bottom sheet opening/closing progress event.""" - - def on_open(self, *args) -> None: - """Event when opening the bottom sheet.""" - - def on_close(self, *args) -> None: - """Event when closing the bottom sheet.""" - - def on_long_touch(self, touch, *args): - if self.ids.drag_handle_container.collide_point(touch.x, touch.y): - self._state = "down" - - def on_touch_down(self, touch): - if self.type == "standard": - super().on_touch_down(touch) - - if self.collide_point(touch.x, touch.y): - self._touch_sheet = not self._touch_sheet - if self.type == "standard": - return True - elif self.type == "modal": - return super().on_touch_down(touch) - - def on_touch_up(self, touch): - self._diff_between_touch_height_sheet = 0 - self._alpha_channel_value = 0 - - if self.collide_point(touch.x, touch.y): - self._touch_sheet = not self._touch_sheet - if self.auto_positioning: - if self._state == "down": - self._set_state(touch.y) - else: - if self._state == "down": - self._touch_sheet = not self._touch_sheet - self._set_state(touch.y) - - def on_touch_move(self, touch): - if self._state == "down": - if not self._diff_between_touch_height_sheet: - self._diff_between_touch_height_sheet = ( - abs(self.y) if self.y else self.height - ) - touch.y - - # FIXME: the behavior of the drag handle looks strange: - # sometimes the bottom sheet is dragged as needed, and sometimes - # it's position does not correspond to the cursor coordinates. - y = -( - (self.height - touch.y) - - 0 # self._diff_between_touch_height_sheet - ) - - if y > 0: - self.y = 0 - return - if self.max_opening_height and touch.y > self.max_opening_height: - self.y = -(self.height - self.max_opening_height) - return - - self.y = y - - if self._scrim_layer and self.type == "modal": - if not self._alpha_channel_value: - self._alpha_channel_value = ( - self._scrim_layer.md_bg_color[-1] - touch.psy - ) - - self._scrim_layer.md_bg_color = self._scrim_layer.md_bg_color[ - :-1 - ] + [touch.psy + self._alpha_channel_value] - - # - # if self.radius == [0.0, 0.0, 0.0, 0.0]: - # self.radius = [16, 16, 0, 0] - - return super().on_touch_move(touch) - - def on_type(self, *args) -> None: - self.add_scrim_layer() - - def add_scrim_layer(self, *args) -> None: - """ - Adds a scrim layer to the parent widget on which the bottom sheet - will be displayed. - """ - - if not self._scrim_layer and self.type == "modal": - self._scrim_layer = BottomSheetScrimLayer() - self.parent.add_widget(self._scrim_layer, index=1) - self._scrim_layer.bind(on_touch_down=self._on_touch_down_layer) - if self._scrim_layer and self.type == "standard": - self.parent.remove_widget(self._scrim_layer) - self._scrim_layer = None - - def check_max_opening_height(self, *args) -> None: - if ( - self.max_opening_height - and self.max_opening_height < self.default_opening_height - ): - raise ValueError( - "The value of `max_opening_height` cannot be less " - "than the value of `default_opening_height`" - ) - - def check_parent(self, *args) -> None: - """ - Checks the type of parent widget to which the bottom sheet - will be added. - """ - - if not issubclass(self.parent.__class__, Screen): - raise TypeError( - f"The bottom sheet can only be added to the {Screen} " - f"or {MDScreen} widgets." - ) - - def dismiss(self, *args) -> None: - """Dismiss of bottom sheet.""" - - anim = Animation( - y=-self.height, - d=self.duration_closing if self.animation else 0, - t=self.closing_transition, - ) - anim.bind( - on_complete=lambda x, y: self.dispatch("on_close"), - on_progress=lambda x, y, z: self.dispatch("on_progress", z), - ) - anim.start(self) - - # Animation( - # radius=[16, 16, 0, 0], - # d=self.duration_closing if self.animation else 0, - # ).start(self) - - if self.type == "modal": - Animation( - md_bg_color=self.scrim_layer_color[:-1] + [0], - d=self.duration_closing if self.animation else 0, - ).start(self._scrim_layer) - - self.state = "close" - - def expand(self) -> None: - """Expand of bottom sheet.""" - - Animation( - y=0 - if not self.max_opening_height - else -(self.height - self.default_opening_height), - d=self.duration_opening if self.animation else 0, - t=self.opening_transition, - ).start(self) - - # Animation( - # radius=[0, 0, 0, 0], - # d=self.duration_opening if self.animation else 0, - # ).start(self) - - def open(self, *args) -> None: - """Opening of bottom sheet.""" - - anim = Animation( - y=-(self.height - self.default_opening_height), - d=self.duration_opening if self.animation else 0, - t=self.opening_transition, - ) - anim.bind( - on_complete=lambda x, y: self.dispatch("on_open"), - on_progress=lambda x, y, z: self.dispatch("on_progress", z), - ) - anim.start(self) - - if self.type == "modal": - alpha_channel_value = 100 / self.parent.height - Animation( - md_bg_color=self.scrim_layer_color[:-1] + [alpha_channel_value], - d=self.duration_opening if self.animation else 0, - ).start(self._scrim_layer) - - self.state = "open" - - def clear_content(self) -> None: - """Removes custom content from the bottom sheet.""" - - self.ids.container.clear_widgets() + self.drawer_type = value def add_widget(self, widget, *args, **kwargs): if isinstance(widget, MDBottomSheetDragHandle): self.ids.drag_handle_container.add_widget(widget) return - elif isinstance(widget, MDBottomSheetContent): - self.ids.container.add_widget(widget) - return return super().add_widget(widget) - def _set_state(self, y): - self._state = "none" - if y < self.height / 2: - self.dismiss() - elif y > self.height / 2: - self.expand() + def on_touch_move(self, touch): + if self.enable_swiping: + if self.status == "closed": + if ( + self.get_dist_from_side(touch.oy) <= self.swipe_edge_width + and abs(touch.y - touch.oy) > self.swipe_distance + ): + self.status = "opening_with_swipe" + elif self.status == "opened": + if abs(touch.y - touch.oy) > self.swipe_distance: + self.status = "closing_with_swipe" - def _on_touch_down_layer(self, instance, touch): - if instance.collide_point(touch.x, touch.y): - if self._touch_sheet: - return True - - if self.state == "open" and not self.auto_dismiss: + if self.status in ("opening_with_swipe", "closing_with_swipe"): + self.open_progress = max( + min( + self.open_progress + + (touch.dy if self.anchor == "left" else -touch.dy) + / self.height, + 1, + ), + 0, + ) return True - elif self.state == "open" and self.auto_dismiss: - self.dismiss() - return True - - -class MDCustomBottomSheet(MDBottomSheet): - """ - .. deprecated:: 1.2.0 - Use :class:`~kivymd.uix.bottomsheet.bottomsheet.MDBottomSheet` - class instead. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - Logger.warning( - "KivyMD: " - "The `MDCustomBottomSheet` class has been deprecated. " - "Use the `MDBottomSheet` class instead." - ) - - -class MDListBottomSheet(MDBottomSheet): - """ - .. deprecated:: 1.2.0 - Use :class:`~kivymd.uix.bottomsheet.bottomsheet.MDBottomSheet` - class instead. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - Logger.warning( - "KivyMD: " - "The `MDListBottomSheet` class has been deprecated. " - "Use the `MDBottomSheet` class instead." - ) - - -class MDGridBottomSheet(MDBottomSheet): - """ - .. deprecated:: 1.2.0 - Use :class:`~kivymd.uix.bottomsheet.bottomsheet.MDBottomSheet` - class instead. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - Logger.warning( - "KivyMD: " - "The `MDGridBottomSheet` class has been deprecated. " - "Use the `MDBottomSheet` class instead." - ) + return super().on_touch_move(touch) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/boxlayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/boxlayout.py index c5f3e75..2c12326 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/boxlayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/boxlayout.py @@ -16,7 +16,7 @@ BoxLayout canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -28,7 +28,7 @@ MDBoxLayout MDBoxLayout: adaptive_height: True - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor Available options are: ---------------------- @@ -38,6 +38,7 @@ Available options are: - adaptive_size_ .. adaptive_height: + adaptive_height --------------- @@ -53,6 +54,7 @@ Equivalent height: self.minimum_height .. adaptive_width: + adaptive_width -------------- @@ -68,6 +70,7 @@ Equivalent height: self.minimum_width .. adaptive_size: + adaptive_size ------------- @@ -89,15 +92,24 @@ from kivy.uix.boxlayout import BoxLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDBoxLayout( - DeclarativeBehavior, ThemableBehavior, BoxLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + BoxLayout, + MDAdaptiveWidget, ): """ Box layout class. - For more information, see in the - :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + For more information see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__init__.py index b833a4c..b908f1f 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__init__.py @@ -1,17 +1,13 @@ # NOQA F401 from .button import ( - BaseButton, - ButtonContentsIconText, - MDFillRoundFlatButton, - MDFillRoundFlatIconButton, - MDFlatButton, - MDFloatingActionButton, - MDFloatingActionButtonSpeedDial, + MDButton, + MDButtonIcon, + MDButtonText, MDIconButton, - MDRaisedButton, - MDRectangleFlatButton, - MDRectangleFlatIconButton, - MDRoundFlatButton, - MDRoundFlatIconButton, - MDTextButton, + MDFabButton, + BaseButton, + BaseFabButton, + MDExtendedFabButton, + MDExtendedFabButtonIcon, + MDExtendedFabButtonText, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/__init__.cpython-311.pyc index 74853bd..a83ac73 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/button.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/button.cpython-311.pyc index 4ce7547..2b3d451 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/button.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/__pycache__/button.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.kv index 77f7d09..908f1fd 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.kv @@ -1,225 +1,391 @@ - - canvas: - Clear - Color: - group: "bg-color" - rgba: - self._md_bg_color \ - if not self.disabled else \ - self._md_bg_color_disabled - RoundedRectangle: - size: self.size - pos: self.pos - source: self.source if hasattr(self, "source") else "" - radius: [root._radius, ] - Color: - group: "outline-color" - rgba: - root._line_color \ - if not root.disabled else \ - (root._line_color_disabled or self._disabled_color) - Line: - width: root.line_width - rounded_rectangle: - ( \ - self.x, self.y, self.width, self.height, \ - root._radius, root._radius, root._radius, root._radius, \ - self.height \ - ) - + size_hint: None, None - anchor_x: root.halign - anchor_y: root.valign - _round_rad: [self._radius] * 4 - - - - lbl_txt: lbl_txt - width: - max( \ - root._min_width, \ - root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \ + text_size: self.size + halign: "center" + valign: "center" + size: + { \ + "standard": ("56dp", "56dp"), \ + "small": ("40dp", "40dp"), \ + "large": ("96dp", "96dp"), \ + }[self.style] + radius: + { \ + "standard": [dp(16), ], \ + "small": [dp(12), ], \ + "large": [dp(28), ], \ + }[self.style] + shadow_radius: + { \ + "standard": [dp(14), ], \ + "small": [dp(10), ], \ + "large": [dp(26), ], \ + }[self.style] + shadow_offset: 0, -1 + elevation_level: + { \ + "standard": 1, \ + "small": 1, \ + "large": 1, \ + }[self.style] + shadow_color: + ( \ + self.theme_cls.shadowColor[:-1] + [.5] \ + if self.theme_shadow_color == "Primary" else \ + self.shadow_color \ + ) \ + if not self.disabled else self.theme_cls.transparentColor + icon_color: + { \ + "surface": self.theme_cls.onPrimaryContainerColor, \ + "secondary": self.theme_cls.onSecondaryContainerColor, \ + "tertiary": self.theme_cls.onTertiaryContainerColor \ + }[self.color_map] \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color \ + if self.icon_color else \ + self.theme_cls.transparentColor \ ) - size_hint_min_x: - max( \ - root._min_width, \ - root.padding[0] + lbl_txt.texture_size[0] + root.padding[2] \ - ) - height: - max( \ - root._min_height, \ - root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \ - ) - size_hint_min_y: - max( \ - root._min_height, \ - root.padding[1] + lbl_txt.texture_size[1] + root.padding[3] \ - ) - - MDLabel: - id: lbl_txt - text: root.text - font_size: root.font_size - font_style: root.font_style - halign: 'center' - valign: 'middle' - adaptive_size: True - -text_size: None, None - theme_text_color: root._theme_text_color - text_color: root._text_color - markup: True - disabled: root.disabled - opposite_colors: root.opposite_colors - font_name: root.font_name if root.font_name else self.font_name - - - - lbl_ic: lbl_ic - size: "48dp", "48dp" - padding: "12dp" if root.icon in md_icons else (0, 0, 0, 0) - # Backwards compatibility. - theme_icon_color: root.theme_icon_color or root.theme_text_color - - MDIcon: - id: lbl_ic - icon: root.icon - font_size: root.icon_size if root.icon_size else self.font_size - font_name: root.font_name if root.font_name else self.font_name - opposite_colors: root.opposite_colors - text_color: - # FIXME: ValueError: None is not allowed for MDIcon.text_color. - # This is only a temporary fix and does not fix the cause of the error. - (root._icon_color if root._icon_color else root.theme_cls.text_color) \ - if not root.disabled else \ - root.theme_cls.disabled_hint_text_color \ - if not root.disabled_color else \ - root.disabled_color - # Fix https://github.com/kivymd/KivyMD/issues/1448 - # TODO: Perhaps this change may affect other widgets. - # You need to create tests. - # on_icon: - # if self.icon not in md_icons.keys(): self.size_hint = (1, 1) - theme_text_color: root._theme_icon_color - - - - lbl_txt: lbl_txt - lbl_ic: lbl_ic - - width: - max( \ - root._min_width, \ - root.padding[0] \ - + lbl_ic.texture_size[0] \ - + box.spacing \ - + lbl_txt.texture_size[0] \ - + root.padding[2] \ - ) - size_hint_min_x: - max( \ - root._min_width, \ - root.padding[0] \ - + lbl_ic.texture_size[0] \ - + box.spacing \ - + lbl_txt.texture_size[0] \ - + root.padding[2] \ - ) - height: - max( \ - root._min_height, \ - root.padding[1] \ - + max(lbl_ic.texture_size[1], lbl_txt.texture_size[1]) \ - + root.padding[3] \ - ) - size_hint_min_y: - max( \ - root._min_height, \ - root.padding[1] \ - + max(lbl_ic.texture_size[1], lbl_txt.texture_size[1]) \ - + root.padding[3] \ - ) - - MDBoxLayout: - id: box - adaptive_size: True - padding: 0 - spacing: "8dp" - - MDIcon: - id: lbl_ic - size_hint_x: None - pos_hint: {"center_y": .5} - icon: root.icon - opposite_colors: root.opposite_colors - font_size: - root.icon_size \ - if root.icon_size else \ - (18 / 14 * lbl_txt.font_size) - text_color: - root._icon_color \ - if not root.disabled else \ - root.theme_cls.disabled_hint_text_color - theme_text_color: root._theme_icon_color - - MDLabel: - id: lbl_txt - adaptive_size: True - -text_size: None, None - pos_hint: {"center_y": .5} - halign: 'center' - valign: 'middle' - text: root.text - font_size: root.font_size - font_style: root.font_style - font_name: root.font_name if root.font_name else self.font_name - theme_text_color: root._theme_text_color - text_color: root._text_color - markup: True - disabled: root.disabled - opposite_colors: root.opposite_colors - - - - adaptive_size: True - color: root.theme_cls.primary_color if not root.color else root.color - opacity: 1 - - - - theme_text_color: "Custom" - md_bg_color: self.theme_cls.primary_color + disabled_color: + self.theme_cls.onSurfaceColor[:-1] + \ + [self.fab_button_opacity_value_disabled_icon] \ + if not self.icon_color_disabled else self.icon_color_disabled + theme_font_size: "Custom" + font_size: + { \ + "standard": "24sp", \ + "small": "24sp", \ + "large": "36sp", \ + }[root.style] canvas.before: Color: rgba: - self.theme_cls.primary_color \ - if not self._bg_color else \ - self._bg_color - RoundedRectangle: - pos: - (self.x - self._canvas_width + dp(1.5)) + self._padding_right / 2, \ - self.y - self._padding_right / 2 + dp(1.5) - size: - self.width + self._canvas_width - dp(3), \ - self.height + self._padding_right - dp(3) - radius: [self.height / 2] - - - - theme_text_color: "Custom" - md_bg_color: self.theme_cls.primary_color - - - - padding_x: "8dp" - padding_y: "8dp" - adaptive_size: True - theme_text_color: "Custom" - - canvas.before: - Color: - rgba: self.bg_color - RoundedRectangle: + { \ + "surface": self.theme_cls.surfaceColor, \ + "secondary": self.theme_cls.secondaryColor, \ + "tertiary": self.theme_cls.tertiaryColor \ + }[self.color_map] \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color + SmoothRoundedRectangle: size: self.size pos: self.pos radius: self.radius + + + + x: "16dp" + icon_color: + ( \ + { \ + "surface": self.theme_cls.onPrimaryContainerColor, \ + "secondary": self.theme_cls.onSecondaryContainerColor, \ + "tertiary": self.theme_cls.onTertiaryContainerColor \ + }[self.parent.color_map] \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color \ + if self.icon_color else self.theme_cls.transparentColor \ + ) \ + ) \ + if self.parent else self.theme_cls.transparentColor + disabled_color: + self.theme_cls.onSurfaceColor[:-1] + \ + [self.fab_button_opacity_value_disabled_icon] \ + if not self.icon_color_disabled else self.icon_color_disabled + pos_hint: {"center_y": .5} + + + + adaptive_width: True + text_color: + ( \ + { \ + "surface": self.theme_cls.onPrimaryContainerColor, \ + "secondary": self.theme_cls.onSecondaryContainerColor, \ + "tertiary": self.theme_cls.onTertiaryContainerColor \ + }[self.parent.color_map] \ + if self.theme_text_color == "Primary" else self.text_color \ + ) \ + if self.parent else self.text_color + disabled_color: + ( \ + self.theme_cls.onSurfaceColor[:-1] + \ + [self.fab_button_opacity_value_disabled_icon] \ + if not self.parent.icon_color_disabled else \ + self.parent.icon_color_disabled \ + ) \ + if self.parent else self.theme_cls.transparentColor + pos_hint: {"center_y": .5} + + + + size_hint: None, None + size: "56dp", "56dp" + radius: [dp(16), ] + shadow_radius: [dp(14), ] + shadow_offset: 0, -1 + # shadow_softness: 2 + elevation_level: 1 + shadow_color: + ( \ + self.theme_cls.shadowColor \ + if self.theme_shadow_color == "Primary" else \ + self.shadow_color \ + ) \ + if not self.disabled else self.theme_cls.transparentColor + theme_font_size: "Custom" + font_size: "24sp" + + canvas.before: + Color: + rgba: + { \ + "standard": self.theme_cls.surfaceContainerColor \ + if self.color_map == "surface" else \ + { \ + "secondary": self.theme_cls.secondaryContainerColor, \ + "tertiary": self.theme_cls.tertiaryContainerColor \ + }[self.color_map], \ + "small": self.theme_cls.surfaceContainerHighColor \ + if self.color_map == "surface" else \ + { \ + "secondary": self.theme_cls.secondaryContainerColor, \ + "tertiary": self.theme_cls.tertiaryColor \ + }[self.color_map], \ + "large": self.theme_cls.surfaceContainerColor \ + if self.color_map == "surface" else \ + { \ + "secondary": self.theme_cls.secondaryContainerColor, \ + "tertiary": self.theme_cls.tertiaryColor \ + }[self.color_map], \ + }[self.style] \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color + SmoothRoundedRectangle: + size: self.size + pos: 0, 0 + radius: self.radius + + + + canvas.before: + Color: + group: "md-icon-button-bg-color" + rgba: + ( \ + { \ + "standard": self.theme_cls.transparentColor, \ + "outlined": self.theme_cls.transparentColor, \ + "tonal": self.theme_cls.secondaryContainerColor, \ + "filled": self.theme_cls.primaryColor, \ + }[self.style] \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color \ + ) \ + if not self.disabled else \ + ( \ + ( \ + { \ + "standard": self.theme_cls.transparentColor, \ + "outlined": self.theme_cls.transparentColor, \ + "tonal": self.theme_cls.onSurfaceColor[:-1] \ + + [self.icon_button_tonal_opacity_value_disabled_container], \ + "filled": self.theme_cls.onSurfaceColor[:-1] \ + + [self.icon_button_filled_opacity_value_disabled_container], \ + }[self.style] \ + ) \ + if not self.md_bg_color_disabled else self.md_bg_color_disabled \ + ) + SmoothRoundedRectangle: + size: self.size + pos: self.pos + radius: self.radius + + radius: [self.height / 2,] + halign: "center" + valign: "center" + size_hint: None, None + size: dp(40), dp(40) + text_size: self.size + line_color: + ( \ + ( \ + self.theme_cls.outlineColor \ + if self.theme_line_color == "Primary" else \ + ( \ + self._line_color \ + if self._line_color else \ + self.line_color \ + ) \ + ) \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + \ + [self.icon_button_outlined_opacity_value_disabled_line] \ + ) \ + if self.style == "outlined" else self.theme_cls.transparentColor + icon_color: + ( \ + { \ + "standard": self.theme_cls.primaryColor, \ + "tonal": self.theme_cls.onSecondaryContainerColor, \ + "filled": self.theme_cls.onPrimaryColor, \ + "outlined": self.theme_cls.onSurfaceVariantColor, \ + }[self.style] \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color \ + if self.icon_color else self.theme_cls.transparentColor \ + ) \ + ) + disabled_color: + { \ + "standard": self.theme_cls.onSurfaceColor[:-1] + \ + [self.icon_button_standard_opacity_value_disabled_icon], \ + "tonal": self.theme_cls.onSurfaceColor[:-1] + \ + [self.icon_button_tonal_opacity_value_disabled_icon], \ + "filled": self.theme_cls.onSurfaceColor[:-1] + \ + [self.icon_button_filled_opacity_value_disabled_icon], \ + "outlined": self.theme_cls.onSurfaceColor[:-1] + \ + [self.icon_button_outlined_opacity_value_disabled_icon], \ + }[self.style] \ + if not self.icon_color_disabled else self.icon_color_disabled + + + + md_bg_color: + { \ + "elevated": self.theme_cls.surfaceContainerLowColor, \ + "filled": self.theme_cls.primaryColor, \ + "tonal": self.theme_cls.secondaryContainerColor, \ + "outlined": self.theme_cls.transparentColor, \ + "text": self.theme_cls.transparentColor, \ + }[self.style] \ + if self.theme_bg_color == "Primary" else self.md_bg_color + line_color: + ( \ + ( \ + self.theme_cls.outlineColor \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_outlined_opacity_value_disabled_line] \ + ) \ + if self.style == "outlined" else \ + self.theme_cls.transparentColor \ + ) \ + if self.theme_line_color == "Primary" else self.line_color + size_hint_x: None if self.theme_width == "Primary" else self.size_hint_x + size_hint_y: None if self.theme_height == "Primary" else self.size_hint_y + height: "40dp" + elevation: self.elevation_levels[self.elevation_level] + shadow_color: + ( \ + ( \ + self.theme_cls.shadowColor[:-1] + [.5] \ + if self.theme_shadow_color == "Primary" else \ + self.shadow_color \ + ) \ + if self.style not in ["outlined", "text"] else \ + self.theme_cls.transparentColor \ + ) \ + if not self.disabled else self.theme_cls.transparentColor + shadow_radius: self.radius + elevation_level: + { \ + "elevated": 1, \ + "filled": 0, \ + "tonal": 0, \ + "outlined": 0, \ + "text": 0, \ + }[self.style] + shadow_offset: [0, -1] if self.style == "elevated" else [0, 0] + + + + adaptive_size: True + pos_hint: {"center_y": .5} + font_style: "Label" + role: "large" + markup: True + disabled: self._button.disabled if self._button else False + text_color: + ( \ + ( \ + ( \ + { \ + "elevated": self.theme_cls.primaryColor, \ + "filled": self.theme_cls.onPrimaryColor, \ + "tonal": self.theme_cls.onSecondaryContainerColor, \ + "outlined": self.theme_cls.primaryColor, \ + "text": self.theme_cls.primaryColor, \ + }[self._button.style] \ + ) \ + if self._button else self.theme_cls.transparentColor \ + ) \ + if self.theme_text_color == "Primary" else self.text_color \ + ) + disabled_color: + ( \ + { \ + "elevated": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_elevated_opacity_value_disabled_text], \ + "filled": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_filled_opacity_value_disabled_text], \ + "tonal": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_tonal_opacity_value_disabled_text], \ + "outlined": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_outlined_opacity_value_disabled_text], \ + "text": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_text_opacity_value_disabled_text], \ + }[self._button.style] \ + ) \ + if self._button else self.theme_cls.transparentColor + + + + size_hint: None, None + size: "18dp", "18dp" + theme_font_size: "Custom" + font_size: "20sp" + x: "16dp" + pos_hint: {"center_y": .5} + icon_color: + ( \ + ( \ + ( \ + { \ + "elevated": self.theme_cls.primaryColor, \ + "filled": self.theme_cls.onPrimaryColor, \ + "tonal": self.theme_cls.onSecondaryContainerColor, \ + "outlined": self.theme_cls.primaryColor, \ + "text": self.theme_cls.primaryColor, \ + }[self._button.style] \ + ) \ + if self._button else self.theme_cls.transparentColor \ + ) \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color \ + if self.icon_color else \ + self.theme_cls.transparentColor \ + ) \ + ) + disabled_color: + ( \ + { \ + "elevated": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_elevated_opacity_value_disabled_icon], \ + "filled": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_filled_opacity_value_disabled_icon], \ + "tonal": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_tonal_opacity_value_disabled_icon], \ + "outlined": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_outlined_opacity_value_disabled_icon], \ + "text": self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_text_opacity_value_disabled_icon], \ + }[self._button.style] \ + if not self.icon_color_disabled else self.icon_color_disabled \ + ) \ + if self._button else self.theme_cls.transparentColor diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.py index 5750a33..bac35d8 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/button/button.py @@ -4,34 +4,52 @@ Components/Button .. seealso:: - `Material Design spec, Buttons `_ - - `Material Design spec, Buttons: floating action button `_ + `Material Design spec, Buttons `_ .. rubric:: Buttons allow users to take actions, and make choices, - with a single tap. + with a single tap. When choosing the right button for an action, consider + the level of emphasis each button type provides. + +KivyMD provides the following button classes for use: .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/buttons.png :align: center -`KivyMD` provides the following button classes for use: +1. Elevated button +2. Filled button +3. Filled tonal button +4. Outlined button +5. Text button +6. Icon button +7. Segmented button +8. Floating action button (FAB) +9. Extended FAB -- MDIconButton_ -- MDFloatingActionButton_ -- MDFlatButton_ -- MDRaisedButton_ -- MDRectangleFlatButton_ -- MDRectangleFlatIconButton_ -- MDRoundFlatButton_ -- MDRoundFlatIconButton_ -- MDFillRoundFlatButton_ -- MDFillRoundFlatIconButton_ -- MDTextButton_ -- MDFloatingActionButtonSpeedDial_ +Common buttons +============== -.. MDIconButton: -MDIconButton ------------- +.. rubric:: Buttons help people take action, such as sending an email, sharing + a document, or liking a comment. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/common-buttons.png + :align: center + +1. Elevated button +2. Filled button +3. Filled tonal button +4. Outlined button +5. Text button + +Elevated_ +Filled_ +Tonal_ +Outlined_ +Text_ + +.. Elevated: + +Elevated +-------- .. tabs:: @@ -45,17 +63,23 @@ MDIconButton KV = ''' MDScreen: + md_bg_color: app.theme_cls.surfaceColor - MDIconButton: - icon: "language-python" + MDButton: + style: "elevated" pos_hint: {"center_x": .5, "center_y": .5} + + MDButtonIcon: + icon: "plus" + + MDButtonText: + text: "Elevated" ''' class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" + self.theme_cls.primary_palette = "Green" return Builder.load_string(KV) @@ -66,343 +90,442 @@ MDIconButton .. code-block:: python from kivymd.app import MDApp - from kivymd.uix.button import MDIconButton + from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText from kivymd.uix.screen import MDScreen class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" + self.theme_cls.primary_palette = "Green" return ( MDScreen( - MDIconButton( - icon="language-python", + MDButton( + MDButtonIcon( + icon="plus", + ), + MDButtonText( + text="Elevated", + ), + style="elevated", pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) + ), + md_bg_color=self.theme_cls.surfaceColor, ) ) Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevated-button.gif :align: center -The :class:`~MDIconButton.icon` parameter must have the name of the icon -from ``kivymd/icon_definitions.py`` file. - -You can also use custom icons: +Common buttons can contain an icon or be without an icon: .. code-block:: kv - MDIconButton: - icon: "kivymd/images/logo/kivymd-icon-256.png" + MDButton: + style: "elevated" + text: "Elevated" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-custom-button.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevated-without-icon-button.png :align: center -By default, :class:`~MDIconButton` button has a size ``(dp(48), dp (48))``. -Use :class:`~BaseButton.icon_size` attribute to resize the button: +.. Filled: + +Filled +------ .. code-block:: kv - MDIconButton: - icon: "android" - icon_size: "64sp" + MDButton: + style: "filled" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-user-font-size.png + MDButtonText: + text: "Filled" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/filled-button.gif :align: center -By default, the color of :class:`~MDIconButton` -(depending on the style of the application) is black or white. -You can change the color of :class:`~MDIconButton` as the text color -of :class:`~kivymd.uix.label.MDLabel`, substituting ``theme_icon_color`` for -``theme_text_color`` and ``icon_color`` for ``text_color``. +.. Tonal: + +Tonal +----- .. code-block:: kv - MDIconButton: - icon: "android" - theme_icon_color: "Custom" - icon_color: app.theme_cls.primary_color + MDButton: + style: "tonal" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-button-theme-text-color.png + MDButtonText: + text: "Tonal" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tonal-button.gif :align: center -.. MDFloatingActionButton: -MDFloatingActionButton ----------------------- +.. Outlined: -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button.png - :align: center - -The above parameters for :class:`~MDIconButton` apply -to :class:`~MDFloatingActionButton`. - -To change :class:`~MDFloatingActionButton` background, use the -``md_bg_color`` parameter: +Outlined +-------- .. code-block:: kv - MDFloatingActionButton: - icon: "android" - md_bg_color: app.theme_cls.primary_color + MDButton: + style: "outlined" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-md-bg-color.png + MDButtonText: + text: "Outlined" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/outlined-button.gif :align: center -Material design style 3 +.. Text: + +Text +---- + +.. code-block:: kv + + MDButton: + style: "text" + + MDButtonText: + text: "Text" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button.gif + :align: center + +Customization of buttons +======================== + +Text positioning and button size +-------------------------------- + +.. code-block:: kv + + MDButton: + style: "tonal" + theme_width: "Custom" + height: "56dp" + size_hint_x: .5 + + MDButtonIcon: + x: text.x - (self.width + dp(10)) + icon: "plus" + + MDButtonText: + id: text + text: "Tonal" + pos_hint: {"center_x": .5, "center_y": .5} + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/positioning-size-button.png + :align: center + +Font of the button text ----------------------- +.. code-block:: kv + + MDButton: + style: "filled" + + MDButtonIcon: + icon: "plus" + + MDButtonText: + text: "Filled" + font_style: "Title" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-style-button-text.png + :align: center + +.. code-block:: kv + + MDButton: + style: "elevated" + + MDButtonText: + text: "Elevated" + theme_font_name: "Custom" + font_name: "path/to/font.ttf" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-button-text.png + :align: center + +Custom button color +------------------- + +.. code-block:: kv + + MDButton: + style: "elevated" + theme_shadow_color: "Custom" + shadow_color: "red" + + MDButtonIcon: + icon: "plus" + theme_icon_color: "Custom" + icon_color: "green" + + MDButtonText: + text: "Elevated" + theme_text_color: "Custom" + text_color: "red" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-color-button.png + :align: center + +Icon buttons +============ + +.. rubric:: Use icon buttons when a compact button is required, such as in a + toolbar or image list. There are two types of icon buttons: standard and + contained. + +.. seealso:: + + `Material Design spec, Icon buttons `_ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-buttons.png + :align: center + +1. Standard icon button +2. Contained icon button (including filled, filled tonal, and outlined styles) + +StandardIcon_ +FilledIcon_ +TonalIcon_ +OutlinedIcon_ + +.. StandardIcon: + +StandardIcon +------------ + +.. code-block:: kv + + MDIconButton: + icon: "heart-outline" + style: "standard" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-standard.gif + :align: center + +.. FilledIcon: + +FilledIcon +---------- + +.. code-block:: kv + + MDIconButton: + icon: "heart-outline" + style: "filled" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-filled.gif + :align: center + +.. TonalIcon: + +TonalIcon +--------- + +.. code-block:: kv + + MDIconButton: + icon: "heart-outline" + style: "tonal" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-tonal.gif + :align: center + +.. OutlinedIcon: + +OutlinedIcon +------------ + +.. code-block:: kv + + MDIconButton: + icon: "heart-outline" + style: "outlined" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-outlined.gif + :align: center + +Custom icon size +---------------- + +.. code-block:: kv + + MDIconButton: + icon: "heart-outline" + style: "tonal" + theme_font_size: "Custom" + font_size: "48sp" + radius: [self.height / 2, ] + size_hint: None, None + size: "84dp", "84dp" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-size.png + :align: center + +Custom button color +------------------- + +.. code-block:: kv + + MDIconButton: + icon: "heart-outline" + style: "tonal" + theme_bg_color: "Custom" + md_bg_color: "brown" + theme_icon_color: "Custom" + icon_color: "white" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-button-color.png + :align: center + +FAB buttons +=========== + +.. rubric:: The FAB represents the most important action on a screen. + It puts key actions within reach. + +.. seealso:: + + `Material Design spec, FAB buttons `_ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-buttons.png + :align: center + +1. Standard FAB +2. Small FAB +3. Large FAB + +There are three sizes of floating action buttons: FAB, small FAB, and large FAB: + +Standard_ +Small_ +Large_ + +.. Standard: + +Standard +-------- + +.. code-block:: kv + + MDFabButton: + icon: "pencil-outline" + style: "standard" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-standard.gif + :align: center + +.. Small: + +Small +----- + +.. code-block:: kv + + MDFabButton: + icon: "pencil-outline" + style: "small" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-small.png + :align: center + +.. Large: + +Large +----- + +.. code-block:: kv + + MDFabButton: + icon: "pencil-outline" + style: "large" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-large.gif + :align: center + +Additional color mappings +------------------------- + +FABs can use other combinations of container and icon colors. The color +mappings below provide the same legibility and functionality as the default, +so the color mapping you use depends on style alone. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-color-mapping.png + :align: center + +1. Surface +2. Secondary +3. Tertiary + .. code-block:: python from kivy.lang import Builder from kivymd.app import MDApp - from kivymd.uix.button import MDFloatingActionButton + from kivymd.uix.button import MDFabButton KV = ''' MDScreen: - md_bg_color: "#f7f2fa" + md_bg_color: app.theme_cls.surfaceColor MDBoxLayout: id: box - spacing: "56dp" adaptive_size: True + spacing: "32dp" pos_hint: {"center_x": .5, "center_y": .5} ''' class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - self.theme_cls.material_style = "M3" + self.theme_cls.primary_palette = "Green" return Builder.load_string(KV) def on_start(self): - data = { - "standard": {"md_bg_color": "#fefbff", "text_color": "#6851a5"}, - "small": {"md_bg_color": "#e9dff7", "text_color": "#211c29"}, - "large": {"md_bg_color": "#f8d7e3", "text_color": "#311021"}, + styles = { + "standard": "surface", + "small": "secondary", + "large": "tertiary", } - for type_button in data.keys(): + for style in styles.keys(): self.root.ids.box.add_widget( - MDFloatingActionButton( - icon="pencil", - type=type_button, - theme_icon_color="Custom", - md_bg_color=data[type_button]["md_bg_color"], - icon_color=data[type_button]["text_color"], + MDFabButton( + style=style, icon="pencil-outline", color_map=styles[style] ) ) Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-m3.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fab-button-color-mapping.png :align: center -.. MDFlatButton: -MDFlatButton ------------- +Extended FAB +============ -To change the text color of: class:`~MDFlatButton` use the ``text_color`` parameter: +.. rubric:: Extended floating action buttons (extended FABs) help people take + primary actions. They're wider than FABs to accommodate a text label and + larger target area. -.. code-block:: kv +.. seealso:: - MDFlatButton: - text: "MDFlatButton" - theme_text_color: "Custom" - text_color: "orange" + `Material Design spec, FAB extended buttons `_ -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-flat-button-text-color.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/extended-fab-button.png :align: center -Or use markup: +1. Extended FAB with both icon and label text +2. Extended FAB without icon -.. code-block:: kv - - MDFlatButton: - text: "[color=#00ffcc]MDFlatButton[/color]" - -To specify the font size and font name, use the parameters as in the usual -`Kivy` buttons: - -.. code-block:: kv - - MDFlatButton: - text: "MDFlatButton" - font_size: "18sp" - font_name: "path/to/font" - -.. MDRaisedButton: -MDRaisedButton --------------- - -This button is similar to the :class:`~MDFlatButton` button except that you -can set the background color for :class:`~MDRaisedButton`: - -.. code-block:: kv - - MDRaisedButton: - text: "MDRaisedButton" - md_bg_color: "red" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-raised-button.png - :align: center - -.. MDRectangleFlatButton: -MDRectangleFlatButton ---------------------- - -.. code-block:: kv - - MDRectangleFlatButton: - text: "MDRectangleFlatButton" - theme_text_color: "Custom" - text_color: "white" - line_color: "red" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-button-md-bg-color.png - :align: center - -.. MDRectangleFlatIconButton: -MDRectangleFlatIconButton -------------------------- - -Button parameters :class:`~MDRectangleFlatIconButton` are the same as -button :class:`~MDRectangleFlatButton`, with the addition of the -``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`. - -.. code-block:: kv - - MDRectangleFlatIconButton: - icon: "android" - text: "MDRectangleFlatIconButton" - theme_text_color: "Custom" - text_color: "white" - line_color: "red" - theme_icon_color: "Custom" - icon_color: "orange" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-custom.png - :align: center - -Without border --------------- - -.. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.button import MDRectangleFlatIconButton - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDRectangleFlatIconButton( - text="MDRectangleFlatIconButton", - icon="language-python", - line_color=(0, 0, 0, 0), - pos_hint={"center_x": .5, "center_y": .5}, - ) - ) - ) - - - Example().run() - -.. code-block:: kv - - MDRectangleFlatIconButton: - text: "MDRectangleFlatIconButton" - icon: "language-python" - line_color: 0, 0, 0, 0 - pos_hint: {"center_x": .5, "center_y": .5} - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-rectangle-flat-icon-button-without-border.png - :align: center - -.. MDRoundFlatButton: -MDRoundFlatButton ------------------ - -.. code-block:: kv - - MDRoundFlatButton: - text: "MDRoundFlatButton" - text_color: "white" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-button-text-color.png - :align: center - -.. MDRoundFlatIconButton: -MDRoundFlatIconButton ---------------------- - -Button parameters :class:`~MDRoundFlatIconButton` are the same as -button :class:`~MDRoundFlatButton`, with the addition of the -``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`: - -.. code-block:: kv - - MDRoundFlatIconButton: - text: "MDRoundFlatIconButton" - icon: "android" - text_color: "white" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-round-flat-icon-button.png - :align: center - -.. MDFillRoundFlatButton: -MDFillRoundFlatButton ---------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-button.png - :align: center - -Button parameters :class:`~MDFillRoundFlatButton` are the same as -button :class:`~MDRaisedButton`. - -.. MDFillRoundFlatIconButton: -MDFillRoundFlatIconButton -------------------------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-fill-round-flat-icon-button.png - :align: center - -Button parameters :class:`~MDFillRoundFlatIconButton` are the same as -button :class:`~MDRaisedButton`, with the addition of the -``theme_icon_color`` and ``icon_color`` parameters as for :class:`~MDIconButton`. - -.. note:: Notice that the width of the :class:`~MDFillRoundFlatIconButton` - button matches the size of the button text. - -.. MDTextButton: -MDTextButton ------------- - -.. code-block:: kv - - MDTextButton: - text: "MDTextButton" - custom_color: "white" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-text-button.png - :align: center - -.. MDFloatingActionButtonSpeedDial: -MDFloatingActionButtonSpeedDial -------------------------------- - -.. Note:: See the full list of arguments in the class - :class:`~MDFloatingActionButtonSpeedDial`. +With icon +--------- .. code-block:: python @@ -412,291 +535,173 @@ MDFloatingActionButtonSpeedDial KV = ''' MDScreen: + md_bg_color: app.theme_cls.surfaceColor + on_touch_down: + if not btn.collide_point(*args[1].pos): \\ + btn.fab_state = "expand" \\ + if btn.fab_state == "collapse" else "collapse" - MDFloatingActionButtonSpeedDial: - data: app.data - root_button_anim: True + MDExtendedFabButton: + id: btn + pos_hint: {"center_x": .5, "center_y": .5} + + MDExtendedFabButtonIcon: + icon: "pencil-outline" + + MDExtendedFabButtonText: + text: "Compose" ''' class Example(MDApp): - data = { - 'Python': 'language-python', - 'PHP': 'language-php', - 'C++': 'language-cpp', - } - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" + self.theme_cls.primary_palette = "Green" return Builder.load_string(KV) Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/extended-fab-button-icon.gif :align: center -Or without KV Language: - -.. tabs:: - - .. tab:: Imperative python style - - .. code-block:: python - - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp - from kivymd.uix.button import MDFloatingActionButtonSpeedDial - - - class Example(MDApp): - data = { - 'Python': 'language-python', - 'PHP': 'language-php', - 'C++': 'language-cpp', - } - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - screen = MDScreen() - speed_dial = MDFloatingActionButtonSpeedDial() - speed_dial.data = self.data - speed_dial.root_button_anim = True - screen.add_widget(speed_dial) - return screen - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.uix.screen import MDScreen - from kivymd.app import MDApp - from kivymd.uix.button import MDFloatingActionButtonSpeedDial - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDFloatingActionButtonSpeedDial( - data={ - 'Python': 'language-python', - 'PHP': 'language-php', - 'C++': 'language-cpp', - }, - root_button_anim=True, - ) - ) - ) - - - Example().run() - -You can use various types of animation of labels for buttons on the stack: +Without icon +------------ .. code-block:: kv - MDFloatingActionButtonSpeedDial: - hint_animation: True + MDExtendedFabButton: + fab_state: "expand" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint.gif + MDExtendedFabButtonText: + text: "Compose" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/extended-fab-button-without-icon.png :align: center -You can set your color values for background, text of buttons etc: +API break +========= + +1.2.0 version +------------- .. code-block:: kv - MDFloatingActionButtonSpeedDial: - hint_animation: True - bg_hint_color: app.theme_cls.primary_dark + MDFloatingActionButton: + icon: "plus" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-hint-color.png - :align: center +.. code-block:: kv -Binds to individual buttons ---------------------------- + MDRoundFlatButton: + text: "Outlined" -.. tabs:: +.. code-block:: kv - .. tab:: Declarative KV style + MDRoundFlatIconButton: + text: "Outlined with icon" + icon: "plus" - .. code-block:: python +.. code-block:: kv - from kivy.lang import Builder - from kivy.properties import DictProperty + MDFillRoundFlatButton + text: "Filled" - from kivymd.app import MDApp +.. code-block:: kv - KV = ''' - MDScreen: + MDFillRoundFlatIconButton + text: "Filled with icon" + icon: "plus" - MDFloatingActionButtonSpeedDial: - id: speed_dial - data: app.data - root_button_anim: True - hint_animation: True - ''' +2.0.0 version +------------- +.. note:: `MDFloatingActionButtonSpeedDial` type buttons were removed + in version `2.0.0`. - class Example(MDApp): - data = DictProperty() +.. code-block:: kv - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - self.data = { - 'Python': 'language-python', - 'JS': [ - 'language-javascript', - "on_press", lambda x: print("pressed JS"), - "on_release", lambda x: print( - "stack_buttons", - self.root.ids.speed_dial.stack_buttons - ) - ], - 'PHP': [ - 'language-php', - "on_press", lambda x: print("pressed PHP"), - "on_release", self.callback - ], - 'C++': [ - 'language-cpp', - "on_press", lambda x: print("pressed C++"), - "on_release", lambda x: self.callback() - ], - } - return Builder.load_string(KV) + MDFabButton: + icon: "plus" - def callback(self, *args): - print(args) +.. code-block:: kv + MDButton: + style: "outlined" - Example().run() + MDButtonText: + text: "Outlined" - .. tab:: Declarative python style +.. code-block:: kv - .. code-block:: python + MDButton: + style: "outlined" - from kivymd.app import MDApp - from kivymd.uix.button import MDFloatingActionButtonSpeedDial - from kivymd.uix.screen import MDScreen + MDButtonIcon: + icon: "plus" + MDButtonText: + text: "Outlined with icon" - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDFloatingActionButtonSpeedDial( - id="speed_dial", - hint_animation=True, - root_button_anim=True, - ) - ) - ) +.. code-block:: kv - def on_start(self): - data = { - "Python": "language-python", - "JS": [ - "language-javascript", - "on_press", lambda x: print("pressed JS"), - "on_release", lambda x: print( - "stack_buttons", - self.root.ids.speed_dial.stack_buttons - ) - ], - "PHP": [ - "language-php", - "on_press", lambda x: print("pressed PHP"), - "on_release", self.callback - ], - "C++": [ - "language-cpp", - "on_press", lambda x: print("pressed C++"), - "on_release", lambda x: self.callback() - ], - } - self.root.ids.speed_dial.data = data + MDButton: + style: "filled" - def callback(self, *args): - print(args) + MDButtonText: + text: "Filled" +.. code-block:: kv - Example().run() + MDButton: + style: "filled" + + MDButtonIcon: + icon: "plus" + + MDButtonText: + text: "Filled" """ from __future__ import annotations __all__ = ( - "BaseButton", "MDIconButton", - "MDFloatingActionButton", - "MDFlatButton", - "MDRaisedButton", - "MDRectangleFlatButton", - "MDRectangleFlatIconButton", - "MDRoundFlatButton", - "MDRoundFlatIconButton", - "MDFillRoundFlatButton", - "MDFillRoundFlatIconButton", - "MDTextButton", - "MDFloatingActionButtonSpeedDial", + "MDButtonText", + "MDButtonIcon", + "MDFabButton", + "MDExtendedFabButton", + "MDExtendedFabButtonIcon", + "MDExtendedFabButtonText", + "MDButton", + "BaseButton", + "BaseFabButton", ) import os -from typing import Union -from kivy.animation import Animation from kivy.clock import Clock -from kivy.core.window import Window from kivy.lang import Builder -from kivy.metrics import dp, sp +from kivy.metrics import dp from kivy.properties import ( - BooleanProperty, - BoundedNumericProperty, ColorProperty, - DictProperty, NumericProperty, - ObjectProperty, OptionProperty, - StringProperty, VariableListProperty, + ObjectProperty, DictProperty, ) -from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.floatlayout import FloatLayout -from kivy.weakproxy import WeakProxy +from kivy.uix.relativelayout import RelativeLayout +from kivymd.uix.label import MDIcon, MDLabel from kivymd import uix_path -from kivymd.color_definitions import text_colors -from kivymd.font_definitions import theme_font_styles -from kivymd.material_resources import ( - FLOATING_ACTION_BUTTON_M2_ELEVATION, - FLOATING_ACTION_BUTTON_M2_OFFSET, - FLOATING_ACTION_BUTTON_M3_ELEVATION, - FLOATING_ACTION_BUTTON_M3_OFFSET, - FLOATING_ACTION_BUTTON_M3_SOFTNESS, - RAISED_BUTTON_OFFSET, - RAISED_BUTTON_SOFTNESS, -) from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( CommonElevationBehavior, DeclarativeBehavior, RectangularRippleBehavior, - RotateBehavior, + BackgroundColorBehavior, ) -from kivymd.uix.label import MDLabel -from kivymd.uix.tooltip import MDTooltip +from kivymd.uix.behaviors.motion_behavior import MotionExtendedFabButtonBehavior +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior with open( os.path.join(uix_path, "button", "button.kv"), encoding="utf-8" @@ -704,192 +709,137 @@ with open( Builder.load_string(kv_file.read()) -theme_text_color_options = ( - "Primary", - "Secondary", - "Hint", - "Error", - "Custom", - "ContrastParentBackground", -) +class BaseFabButton: + """ + Implements the basic properties for the + :class:`~MDExtendedFabButton` and :class:`~MDFabButton` classes. + + .. versionadded:: 2.0.0 + """ + + elevation_levels = DictProperty( + { + 0: 0, + 1: dp(4), + 2: dp(8), + 3: dp(12), + 4: dp(16), + 5: dp(18), + } + ) + """ + Elevation is measured as the distance between components along the z-axis + in density-independent pixels (dps). + + .. versionadded:: 1.2.0 + + :attr:`elevation_levels` is an :class:`~kivy.properties.DictProperty` + and defaults to `{0: dp(0), 1: dp(4), 2: dp(8), 3: dp(12), 4: dp(16), 5: dp(18)}`. + """ + + color_map = OptionProperty( + "surface", options=("surface", "secondary", "tertiary") + ) + """ + Additional color mappings. + + Available options are: 'surface', 'secondary', 'tertiary'. + + :attr:`color_map` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'secondary'`. + """ + + icon_color_disabled = ColorProperty(None) + """ + The icon color in (r, g, b, a) or string format of the list item when + the widget item is disabled. + + :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + style = OptionProperty("standard", options=("standard", "small", "large")) + """ + Button type. + + Available options are: 'standard', 'small', 'large'. + + :attr:`style` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'standard'`. + """ + + fab_state = OptionProperty("collapse", options=("collapse", "expand")) + """ + The state of the button. + + Available options are: 'collapse' or 'expand'. + + :attr:`fab_state` is an :class:`~kivy.properties.OptionProperty` + and defaults to "collapse". + """ + + md_bg_color_disabled = ColorProperty(None) + """ + The background color in (r, g, b, a) or string format of the list item when + the list button is disabled. + + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + radius = VariableListProperty( + [ + dp(16), + ], + length=4, + ) + """ + Canvas radius. + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(16), dp(16), dp(16), dp(16)]`. + """ class BaseButton( DeclarativeBehavior, + BackgroundColorBehavior, RectangularRippleBehavior, - ThemableBehavior, ButtonBehavior, - AnchorLayout, + ThemableBehavior, + StateLayerBehavior, ): """ - Base class for all buttons. + Base button class. For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and - :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivy.uix.anchorlayout.AnchorLayout` + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` classes documentation. """ - padding = VariableListProperty([dp(16), dp(8), dp(16), dp(8)]) + elevation_levels = DictProperty( + { + 0: 0, + 1: dp(4), + 2: dp(8), + 3: dp(12), + 4: dp(16), + 5: dp(18), + } + ) """ - Padding between the widget box and its children, in pixels: - [padding_left, padding_top, padding_right, padding_bottom]. + Elevation is measured as the distance between components along the z-axis + in density-independent pixels (dps). - padding also accepts a two argument form [padding_horizontal, - padding_vertical] and a one argument form [padding]. + .. versionadded:: 1.2.0 - .. versionadded:: 1.0.0 - - :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` - and defaults to [16dp, 8dp, 16dp, 8dp]. - """ - - halign = OptionProperty("center", options=("left", "center", "right")) - """ - Horizontal anchor. - - .. versionadded:: 1.0.0 - - :attr:`anchor_x` is an :class:`~kivy.properties.OptionProperty` - and defaults to 'center'. It accepts values of 'left', 'center' or 'right'. - """ - - valign = OptionProperty("center", options=("top", "center", "bottom")) - """ - Vertical anchor. - - .. versionadded:: 1.0.0 - - :attr:`anchor_y` is an :class:`~kivy.properties.OptionProperty` - and defaults to 'center'. It accepts values of 'top', 'center' or 'bottom'. - """ - - text = StringProperty("") - """ - Button text. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon = StringProperty("") - """ - Button icon. - - :attr:`icon` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - font_style = OptionProperty("Body1", options=theme_font_styles) - """ - Button text font style. - - Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, - `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`, - `'Caption'`, `'Overline'`, `'Icon'`. - - :attr:`font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - theme_text_color = OptionProperty(None, options=theme_text_color_options) - """ - Button text type. Available options are: (`"Primary"`, `"Secondary"`, - `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`). - - :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None` (set by button class). - """ - - theme_icon_color = OptionProperty(None, options=theme_text_color_options) - """ - Button icon type. Available options are: (`"Primary"`, `"Secondary"`, - `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`). - - .. versionadded:: 1.0.0 - - :attr:`theme_icon_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None` (set by button subclass). - """ - - text_color = ColorProperty(None) - """ - Button text color in (r, g, b, a) or string format. - - :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_color = ColorProperty(None) - """ - Button icon color in (r, g, b, a) or string format. - - :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_name = StringProperty() - """ - Button text font name. - - :attr:`font_name` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - font_size = NumericProperty("14sp") - """ - Button text font size. - - :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` - and defaults to `14sp`. - """ - - icon_size = NumericProperty() - """ - Icon font size. - Use this parameter as the font size, that is, in sp units. - - .. versionadded:: 1.0.0 - - :attr:`icon_size` is a :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - line_width = NumericProperty(1) - """ - Line width for button border. - - :attr:`line_width` is a :class:`~kivy.properties.NumericProperty` - and defaults to `1`. - """ - - line_color = ColorProperty(None) - """ - Line color in (r, g, b, a) or string format for button border. - - :attr:`line_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - line_color_disabled = ColorProperty(None) - """ - Disabled line color in (r, g, b, a) or string format for button border. - - .. versionadded:: 1.0.0 - - :attr:`line_color_disabled` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - md_bg_color = ColorProperty(None) - """ - Button background color in (r, g, b, a) or string format. - - :attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. + :attr:`elevation_levels` is an :class:`~kivy.properties.DictProperty` + and defaults to `{0: dp(0), 1: dp(4), 2: dp(8), 3: dp(12), 4: dp(16), 5: dp(18)}`. """ md_bg_color_disabled = ColorProperty(None) @@ -901,1467 +851,439 @@ class BaseButton( and defaults to `None`. """ - disabled_color = ColorProperty(None) + shadow_radius = VariableListProperty([0, 0, 0, 0]) """ - The color of the text and icon when the button is disabled, - in (r, g, b, a) or string format. + Button shadow radius. - .. versionadded:: 1.0.0 + :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ - :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty` + md_bg_color = ColorProperty(None) + """ + Button background color in (r, g, b, a) or string format. + + :attr:`md_bg_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - rounded_button = BooleanProperty(False) + line_color = ColorProperty(None) """ - Should the button have fully rounded corners (e.g. like M3 buttons)? + Outlined color. - .. versionadded:: 1.0.0 - - :attr:`rounded_button` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. + :attr:`line_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - # Note - _radius must be > 0 to avoid rendering issues. - _radius = BoundedNumericProperty(dp(4), min=0.0999, errorvalue=0.1) - # Properties used for rendering. - _disabled_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - _md_bg_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - _md_bg_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0]) - _line_color = ColorProperty([0.0, 0.0, 0.0, 0.0]) - _line_color_disabled = ColorProperty([0.0, 0.0, 0.0, 0.0]) - _theme_text_color = OptionProperty(None, options=theme_text_color_options) - _theme_icon_color = OptionProperty(None, options=theme_text_color_options) - _text_color = ColorProperty(None) - _icon_color = ColorProperty(None) + line_width = NumericProperty(1) + """ + Line width for button border. - # Defaults which can be overridden in subclasses - _min_width = NumericProperty(dp(64)) - _min_height = NumericProperty(dp(36)) + :attr:`line_width` is a :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ - # Default colors - set to None to use primary theme colors - _default_md_bg_color = [0.0, 0.0, 0.0, 0.0] - _default_md_bg_color_disabled = [0.0, 0.0, 0.0, 0.0] - _default_line_color = [0.0, 0.0, 0.0, 0.0] - _default_line_color_disabled = [0.0, 0.0, 0.0, 0.0] - _default_theme_text_color = StringProperty("Primary") - _default_theme_icon_color = StringProperty("Primary") - _default_text_color = ColorProperty(None) - _default_icon_color = ColorProperty(None) + def on_press(self, *args) -> None: + """Fired when the button is pressed.""" - _animation_fade_bg = ObjectProperty(None, allownone=True) + self._on_press(args) + + def on_release(self, *args) -> None: + """ + Fired when the button is released + (i.e. the touch/click that pressed the button goes away). + """ + + self._on_release(args) + + +class MDButton(BaseButton, CommonElevationBehavior, RelativeLayout): + """ + Base class for all buttons. + + .. versionadded:: 2.2.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~BaseButton` and + :class:`~kivy.uix.relativelayout.RelativeLayout` + classes documentation. + """ + + style = OptionProperty( + "elevated", options=("elevated", "filled", "tonal", "outlined", "text") + ) + """ + Button type. + + Available options are: 'filled', 'elevated', 'outlined', 'tonal', 'text'. + + :attr:`style` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'elevated'`. + """ + + radius = VariableListProperty( + [ + dp(20), + ] + ) + """ + Button radius. + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(20), dp(20), dp(20), dp(20)]`. + """ + + # kivymd.uix.button.button.MDButtonIcon object. + _button_icon = ObjectProperty() + # kivymd.uix.button.button.MDButtonText object. + _button_text = ObjectProperty() + + _icon_left_pad = dp(16) + _spacing_between_icon_text = dp(10) + _text_right_pad = dp(24) + _text_left_pad = dp(24) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.theme_cls.bind( - primary_palette=self.set_all_colors, - theme_style=self.set_all_colors, - ) - self.bind( - md_bg_color=self.set_button_colors, - md_bg_color_disabled=self.set_button_colors, - line_color=self.set_button_colors, - line_color_disabled=self.set_button_colors, - theme_text_color=self.set_text_color, - text_color=self.set_text_color, - theme_icon_color=self.set_icon_color, - icon_color=self.set_icon_color, - disabled_color=self.set_disabled_color, - rounded_button=self.set_radius, - height=self.set_radius, - ) - Clock.schedule_once(self.set_all_colors) - Clock.schedule_once(self.set_radius) + Clock.schedule_once(self.adjust_width, 0.2) + Clock.schedule_once(self.adjust_pos, 0.2) - def set_disabled_color(self, *args): - """ - Sets the color for the icon, text and line of the button when button - is disabled. - """ + def adjust_pos(self, *args) -> None: + """Adjusts the pos of the button according to the content.""" - if self.disabled: - disabled_color = ( - self.disabled_color - if self.disabled_color - else self.theme_cls.disabled_hint_text_color + if self._button_icon and self._button_text: + self._button_text.x = ( + self._button_icon.x + + self._spacing_between_icon_text + + self._icon_left_pad + + dp(2) ) - self._disabled_color = disabled_color - # Button icon color. - if "lbl_ic" in self.ids: - self.ids.lbl_ic.disabled_color = disabled_color - # Button text color. - if "lbl_txt" in self.ids: - self.ids.lbl_txt.disabled_color = disabled_color - else: - self._disabled_color = self._line_color + elif not self._button_icon and self._button_text: + self._button_text.x = self._text_left_pad - def set_all_colors(self, *args) -> None: - """Set all button colours.""" + def adjust_width(self, *args) -> None: + """Adjusts the width of the button according to the content.""" - self.set_button_colors() - self.set_text_color() - self.set_icon_color() - - def set_button_colors(self, *args) -> None: - """Set all button colours (except text/icons).""" - - # Set main color - _md_bg_color = ( - self.md_bg_color - or self._default_md_bg_color - or self.theme_cls.primary_color - ) - - # Set disabled color - _md_bg_color_disabled = ( - self.md_bg_color_disabled - or ( - [sum(self.md_bg_color[0:3]) / 3.0] * 3 - + [0.38 if self.theme_cls.theme_style == "Light" else 0.5] - if self.md_bg_color - else None - ) - or self._default_md_bg_color_disabled - or self.theme_cls.disabled_primary_color - ) - - # Set line color - _line_color = ( - self.line_color - or self._default_line_color - or self.theme_cls.primary_color - ) - - # Set disabled line color - _line_color_disabled = ( - self.line_color_disabled - or ( - [sum(self.line_color[0:3]) / 3.0] * 3 - + [0.38 if self.theme_cls.theme_style == "Light" else 0.5] - if self.line_color - else None - ) - or self._default_line_color_disabled - or self.theme_cls.disabled_primary_color - ) - - if self.theme_cls.theme_style_switch_animation: - Animation( - _md_bg_color=_md_bg_color, - _md_bg_color_disabled=_md_bg_color_disabled, - _line_color=_line_color, - _line_color_disabled=_line_color_disabled, - d=self.theme_cls.theme_style_switch_animation_duration, - t="linear", - ).start(self) - else: - self._md_bg_color = _md_bg_color - self._md_bg_color_disabled = _md_bg_color_disabled - self._line_color = _line_color - self._line_color_disabled = _line_color_disabled - - def set_text_color(self, *args) -> None: - """ - Set _theme_text_color and _text_color based on defaults and options. - """ - - self._theme_text_color = ( - self.theme_text_color or self._default_theme_text_color - ) - if self._default_text_color == "PrimaryHue": - default_text_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - elif self._default_text_color == "Primary": - default_text_color = self.theme_cls.primary_color - else: - default_text_color = self.theme_cls.text_color - self._text_color = self.text_color or default_text_color - - def set_icon_color(self, *args) -> None: - """ - Set _theme_icon_color and _icon_color based on defaults and options. - """ - - self._theme_icon_color = ( - (self.theme_icon_color or self._default_theme_icon_color) - if not self.disabled - else "Custom" - ) - if self._default_icon_color == "PrimaryHue": - default_icon_color = text_colors[self.theme_cls.primary_palette][ - self.theme_cls.primary_hue - ] - elif self._default_icon_color == "Primary": - default_icon_color = self.theme_cls.primary_color - else: - default_icon_color = self.theme_cls.text_color - self._icon_color = self.icon_color or default_icon_color - - def set_radius(self, *args) -> None: - """ - Set the radius, if we are a rounded button, based on the - current height. - """ - - if self.rounded_button: - self._radius = self.height / 2 - - # Touch events that cause transparent buttons to fade to background - def on_touch_down(self, touch): - """ - Animates fade to background on press, for buttons with no - background color. - """ - - if touch.is_mouse_scrolling: - return False - elif not self.collide_point(touch.x, touch.y): - return False - elif self in touch.ud: - return False - elif self.disabled: - return False - else: - if self._md_bg_color[3] == 0.0: - self._animation_fade_bg = Animation( - duration=0.5, _md_bg_color=[0.0, 0.0, 0.0, 0.1] + if self._button_icon and self._button_text: + if self.theme_width == "Primary": + self.width = ( + self._button_icon.texture_size[0] + + self._button_text.texture_size[0] + + self._icon_left_pad + + self._spacing_between_icon_text + + self._text_right_pad + ) + elif not self._button_icon and self._button_text: + if self.theme_width == "Primary": + self.width = ( + self._button_text.texture_size[0] + + self._text_left_pad + + self._text_right_pad + ) + elif self._button_icon and not self._button_text: + if self.theme_width == "Primary": + self.width = ( + dp(48) + + self._button_icon.texture_size[0] + - self._spacing_between_icon_text ) - self._animation_fade_bg.start(self) - return super().on_touch_down(touch) - def on_touch_up(self, touch): - """Animates return to original background on touch release.""" - - if not self.disabled and self._animation_fade_bg: - self._animation_fade_bg.stop_property(self, "_md_bg_color") - self._animation_fade_bg = None - md_bg_color = ( - self.md_bg_color - or self._default_md_bg_color - or self.theme_cls.primary_color + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDButtonText): + self._button_text = widget + widget.bind( + text=lambda x, y: Clock.schedule_once(self.adjust_width, 0.2) ) - Animation(duration=0.05, _md_bg_color=md_bg_color).start(self) - return super().on_touch_up(touch) + widget._button = self + elif isinstance(widget, MDButtonIcon): + self._button_icon = widget + widget._button = self + if isinstance(widget, (MDButtonIcon, MDButtonText)): + return super().add_widget(widget) - def on_disabled(self, instance_button, disabled_value: bool) -> None: - if hasattr(super(), "on_disabled"): - if self.disabled is True: - Animation.cancel_all(self, "elevation") - super().on_disabled(instance_button, disabled_value) - Clock.schedule_once(self.set_disabled_color) + def set_properties_widget(self) -> None: + """Fired `on_release/on_press/on_enter/on_leave` events.""" + + super().set_properties_widget() + + if ( + self._state == self.state_hover + and self.focus_behavior + or self._state == self.state_press + ): + self._elevation_level = ( + 1 + if self.theme_elevation_level == "Primary" + else self.elevation_level + ) + self._shadow_softness = ( + 0 + if self.theme_shadow_softness == "Primary" + else self.shadow_softness + ) + + if not self.disabled: + if self._state == self.state_hover and self.focus_behavior: + if self.style == "elevated": + self.elevation_level = 2 + self.shadow_softness = 2 + elif self._state == self.state_press: + if self.style == "elevated": + self.elevation_level = 2 + self.shadow_softness = 2 + elif not self._state: + if self.style == "elevated": + self.elevation_level = 1 + self.shadow_softness = 0 -class ButtonElevationBehaviour(CommonElevationBehavior): +class MDButtonText(MDLabel): """ - Implements elevation behavior as well as the recommended down/disabled - colors for raised buttons. - - The minimum elevation for any raised button is `'1dp'`, - by default, set to `'2dp'`. - - The `_elevation_raised` is automatically computed and is set to - `self.elevation + 6` each time `self.elevation` is updated. - """ - - _elevation_raised = NumericProperty() - _anim_raised = ObjectProperty(None, allownone=True) - _default_elevation = 2 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if self.elevation == 0: - self.elevation = self._default_elevation - if hasattr(self, "radius"): - self.bind(_radius=self.setter("radius")) - Clock.schedule_once(self.create_anim_raised) - self.on_disabled(self, self.disabled) - - def create_anim_raised(self, *args) -> None: - if self.elevation: - self._elevation_raised = self.elevation - self._anim_raised = Animation(elevation=self.elevation + 1, d=0.15) - - def on_touch_down(self, touch): - if not self.disabled: - if touch.is_mouse_scrolling: - return False - if not self.collide_point(touch.x, touch.y): - return False - if self in touch.ud: - return False - if self._anim_raised and self.elevation: - self._anim_raised.start(self) - return super().on_touch_down(touch) - - def on_touch_up(self, touch): - if not self.disabled: - if self in touch.ud: - self.stop_elevation_anim() - return super().on_touch_up(touch) - return super().on_touch_up(touch) - - def stop_elevation_anim(self): - Animation.cancel_all(self, "elevation") - if self._anim_raised and self.elevation: - self.elevation = self._elevation_raised - - -class ButtonContentsText: - """Contents for :class:`~BaseButton` class consisting of a single label.""" - - -class ButtonContentsIcon: - """ - Contents for a round BaseButton consisting of an :class:`~MDIcon` class. - """ - - _min_width = NumericProperty(0) - - def on_text_color(self, instance_button, color: list) -> None: - """ - Set icon_color equal to text_color. - For backwards compatibility - can use text_color instead - of icon_color. - """ - - if color: - self.icon_color = color - - -class ButtonContentsIconText: - """ - Contents for :class:`~BaseButton` class consisting of a - :class:`~kivy.uix.boxlayout.BoxLayout` with an icon and a label. - """ - - padding = VariableListProperty([dp(12), dp(8), dp(16), dp(8)]) - """ - Padding between the widget box and its children, in pixels: - [padding_left, padding_top, padding_right, padding_bottom]. - - padding also accepts a two argument form [padding_horizontal, - padding_vertical] and a one argument form [padding]. - - .. versionadded:: 1.0.0 - - :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` - and defaults to [12dp, 8dp, 16dp, 8dp]. - """ - - -# Old MD Button classes - - -class OldButtonIconMixin: - """Backwards-compatibility for icons.""" - - icon = StringProperty("android") - - def on_icon_color(self, instance_button, color: list) -> None: - """ - If we are setting an icon color, set theme_icon_color to Custom. - For backwards compatibility (before theme_icon_color existed). - """ - - if color and (self.theme_text_color == "Custom"): - self.theme_icon_color = "Custom" - - -class MDFlatButton(BaseButton, ButtonContentsText): - """ - A flat rectangular button with (by default) no border or background. - Text is the default text color. + The class implements the text for the :class:`~MDButton` class. For more information, see in the - :class:`~BaseButton` and :class:`~ButtonContentsText` + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + # kivymd.uix.button.button.MDButton object. + _button = ObjectProperty() + + +class MDButtonIcon(MDIcon): + """ + The class implements an icon for the :class:`~MDButton` class. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDIcon` class documentation. + """ + + # kivymd.uix.button.button.MDButton object. + _button = ObjectProperty() + + +class MDIconButton(RectangularRippleBehavior, ButtonBehavior, MDIcon): + """ + Base class for icon buttons. + + For more information, see in the + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivy.uix.label.label.MDIcon` classes documentation. """ - padding = VariableListProperty([dp(8), dp(8), dp(8), dp(8)]) + style = OptionProperty( + "standard", options=("standard", "filled", "tonal", "outlined") + ) """ - Padding between the widget box and its children, in pixels: - [padding_left, padding_top, padding_right, padding_bottom]. + Button type. - padding also accepts a two argument form [padding_horizontal, - padding_vertical] and a one argument form [padding]. + .. versionadded:: 2.0.0 - .. versionadded:: 1.0.0 + Available options are: 'standard', 'filled', 'tonal', 'outlined'. - :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` - and defaults to [8dp, 8dp, 8dp, 8dp]. - """ - - -class MDRaisedButton(BaseButton, ButtonElevationBehaviour, ButtonContentsText): - """ - A flat button with (by default) a primary color fill and matching - color text. - - For more information, see in the - :class:`~BaseButton` and - :class:`~ButtonElevationBehaviour` and - :class:`~ButtonContentsText` - classes documentation. - """ - - # FIXME: Move the underlying attributes to the :class:`~BaseButton` class. - # This applies to all classes of buttons that have similar attributes. - _default_md_bg_color = None - _default_md_bg_color_disabled = None - _default_theme_text_color = "Custom" - _default_text_color = "PrimaryHue" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.shadow_softness = RAISED_BUTTON_SOFTNESS - self.shadow_offset = RAISED_BUTTON_OFFSET - # self.shadow_radius = self._radius * 2 - - -class MDRectangleFlatButton(BaseButton, ButtonContentsText): - """ - A flat button with (by default) a primary color border and primary - color text. - - For more information, see in the - :class:`~BaseButton` and :class:`~ButtonContentsText` - classes documentation. - """ - - _default_line_color = None - _default_line_color_disabled = None - _default_theme_text_color = "Custom" - _default_text_color = "Primary" - - -class MDRectangleFlatIconButton( - BaseButton, OldButtonIconMixin, ButtonContentsIconText -): - """ - A flat button with (by default) a primary color border, primary color text - and a primary color icon on the left. - - For more information, see in the - :class:`~BaseButton` and - :class:`~OldButtonIconMixin` and - :class:`~ButtonContentsIconText` - classes documentation. - """ - - _default_line_color = None - _default_line_color_disabled = None - _default_theme_text_color = "Custom" - _default_theme_icon_color = "Custom" - _default_text_color = "Primary" - _default_icon_color = "Primary" - - -class MDRoundFlatButton(BaseButton, ButtonContentsText): - """ - A flat button with (by default) fully rounded corners, a primary - color border and primary color text. - - For more information, see in the - :class:`~BaseButton` and :class:`~ButtonContentsText` - classes documentation. - """ - - _default_line_color = None - _default_line_color_disabled = None - _default_theme_text_color = "Custom" - _default_text_color = "Primary" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.rounded_button = True - - -class MDRoundFlatIconButton( - BaseButton, OldButtonIconMixin, ButtonContentsIconText -): - """ - A flat button with (by default) rounded corners, a primary color border, - primary color text and a primary color icon on the left. - - For more information, see in the - :class:`~BaseButton` and - :class:`~OldButtonIconMixin` and - :class:`~ButtonContentsIconText` - classes documentation. - """ - - _default_line_color = None - _default_line_color_disabled = None - _default_theme_text_color = "Custom" - _default_theme_icon_color = "Custom" - _default_text_color = "Primary" - _default_icon_color = "Primary" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.rounded_button = True - - -class MDFillRoundFlatButton(BaseButton, ButtonContentsText): - """ - A flat button with (by default) rounded corners, a primary color fill - and primary color text. - - For more information, see in the - :class:`~BaseButton` and :class:`~ButtonContentsText` - classes documentation. - """ - - _default_md_bg_color = None - _default_md_bg_color_disabled = None - _default_theme_text_color = "Custom" - _default_text_color = "PrimaryHue" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.rounded_button = True - - -class MDFillRoundFlatIconButton( - BaseButton, OldButtonIconMixin, ButtonContentsIconText -): - """ - A flat button with (by default) rounded corners, a primary color fill, - primary color text and a primary color icon on the left. - - For more information, see in the - :class:`~BaseButton` and - :class:`~OldButtonIconMixin` and - :class:`~ButtonContentsIconText` - classes documentation. - """ - - _default_md_bg_color = None - _default_md_bg_color_disabled = None - _default_theme_text_color = "Custom" - _default_theme_icon_color = "Custom" - _default_text_color = "PrimaryHue" - _default_icon_color = "PrimaryHue" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.rounded_button = True - - -class MDIconButton(BaseButton, OldButtonIconMixin, ButtonContentsIcon): - """ - A simple rounded icon button. - - For more information, see in the - :class:`~BaseButton` and - :class:`~OldButtonIconMixin` and - :class:`~ButtonContentsIcon` classes documentation. - """ - - icon = StringProperty("checkbox-blank-circle") - """ - Button icon. - - :attr:`icon` is a :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank-circle'`. - """ - - _min_width = NumericProperty(0) - _default_icon_pad = max(dp(48) - sp(24), 0) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.rounded_button = True - # FIXME: GraphicException: Invalid width value, must be > 0 - self.line_width = 0.001 - Clock.schedule_once(self.set_size) - - def set_size(self, interval: Union[int, float]) -> None: - """ - Sets the icon width/height based on the current `icon_size` - attribute, or the default value if it is zero. The icon size - is set to `(48, 48)` for an icon with the default font_size 24sp. - """ - diameter = self._default_icon_pad + (self.icon_size or sp(24)) - self.width = diameter - self.height = diameter - - -class MDFloatingActionButton( - BaseButton, OldButtonIconMixin, ButtonElevationBehaviour, ButtonContentsIcon -): - """ - Implementation - `FAB `_ - button. - - For more information, see in the - :class:`~BaseButton` and - :class:`~OldButtonIconMixin` and - :class:`~ButtonElevationBehaviour` and - :class:`~ButtonContentsIcon` classes documentation. - """ - - type = OptionProperty("standard", options=["small", "large", "standard"]) - """ - Type of M3 button. - - .. versionadded:: 1.0.0 - - Available options are: 'small', 'large', 'standard'. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-floating-action-button-types.png - :align: center - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` + :attr:`style` is an :class:`~kivy.properties.OptionProperty` and defaults to `'standard'`. """ - _default_md_bg_color = None - _default_md_bg_color_disabled = None - _default_theme_icon_color = "Custom" - _default_icon_color = "PrimaryHue" + md_bg_color_disabled = ColorProperty(None) + """ + The background color in (r, g, b, a) or string format of the list item when + the list button is disabled. + + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + _line_color = ColorProperty(None) + + def on_line_color(self, instance, value) -> None: + """Fired when the values of :attr:`line_color` change.""" + + if not self.disabled and self.theme_line_color == "Custom": + self._line_color = value + + +class MDFabButton( + BaseFabButton, + CommonElevationBehavior, + RectangularRippleBehavior, + ButtonBehavior, + MDIcon, +): + """ + Base class for FAB buttons. + + For more information, see in the + :class:`~BaseFabButton` and + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.label.label.MDIcon` + classes documentation. + """ + + def on_press(self, *args) -> None: + """Fired when the button is pressed.""" + + self._on_press(args) + + def on_release(self, *args) -> None: + """ + Fired when the button is released + (i.e. the touch/click that pressed the button goes away). + """ + + self._on_release(args) + + def set_properties_widget(self) -> None: + """Fired `on_release/on_press/on_enter/on_leave` events.""" + + super().set_properties_widget() + + if ( + self._state == self.state_hover + and self.focus_behavior + or self._state == self.state_press + ): + self._elevation_level = ( + 1 + if self.theme_elevation_level == "Primary" + else self.elevation_level + ) + self._shadow_softness = ( + 0 + if self.theme_shadow_softness == "Primary" + else self.shadow_softness + ) + + if not self.disabled: + if ( + self._state == self.state_hover and self.focus_behavior + ): + self.elevation_level = 1 + self.shadow_softness = 0 + elif self._state == self.state_press: + self.elevation_level = 2 + self.shadow_softness = 2 + elif not self._state: + self.elevation_level = 1 + self.shadow_softness = 0 + + +class MDExtendedFabButtonIcon(MDIcon): + """ + Implements an icon for the :class:`~MDExtendedFabButton` class. + + .. versionadded:: 2.0.0 + """ + + +class MDExtendedFabButtonText(MDLabel): + """ + Implements the text for the class :class:`~MDExtendedFabButton` class. + + .. versionadded:: 2.0.0 + """ + + +class MDExtendedFabButton( + DeclarativeBehavior, + ThemableBehavior, + MotionExtendedFabButtonBehavior, + CommonElevationBehavior, + StateLayerBehavior, + BaseFabButton, + ButtonBehavior, + RelativeLayout, +): + """ + Base class for Extended FAB buttons. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.motion_behavior.MotionExtendedFabButtonBehavior` and + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and + :class:`~BaseFabButton` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivy.uix.relativelayout.RelativeLayout` + classes documentation. + + :Events: + `on_collapse` + Fired when the button is collapsed. + `on_expand` + Fired when the button is expanded. + """ + + elevation_levels = DictProperty( + { + 0: 0, + 1: dp(4), + 2: dp(8), + 3: dp(12), + 4: dp(16), + 5: dp(18), + } + ) + """ + Elevation is measured as the distance between components along the z-axis + in density-independent pixels (dps). + + .. versionadded:: 1.2.0 + + :attr:`elevation_levels` is an :class:`~kivy.properties.DictProperty` + and defaults to `{0: dp(0), 1: dp(4), 2: dp(8), 3: dp(12), 4: dp(16), 5: dp(18)}`. + """ + + _icon = ObjectProperty() + _label = ObjectProperty() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # FIXME: GraphicException: Invalid width value, must be > 0 - self.line_width = 0.001 - self.theme_cls.bind(material_style=self.set_size_and_radius) - Clock.schedule_once(self.set_size) - Clock.schedule_once(self.set__radius) - Clock.schedule_once(self.set_font_size) - - def set_font_size(self, *args) -> None: - if self.theme_cls.material_style == "M3": - if self.type == "large": - self.icon_size = "36sp" - else: - self.icon_size = 0 - - def set__radius(self, *args) -> None: - if self.theme_cls.material_style == "M2": - self.shadow_radius = self.height / 2 - self.elevation = FLOATING_ACTION_BUTTON_M2_ELEVATION - self.shadow_offset = FLOATING_ACTION_BUTTON_M2_OFFSET - self.rounded_button = True - else: - self.shadow_softness = FLOATING_ACTION_BUTTON_M3_SOFTNESS - self.shadow_offset = FLOATING_ACTION_BUTTON_M3_OFFSET - self.elevation = FLOATING_ACTION_BUTTON_M3_ELEVATION - self.rounded_button = False - - if self.type == "small": - self._radius = dp(12) - elif self.type == "standard": - self._radius = dp(16) - elif self.type == "large": - self._radius = dp(28) - - self.shadow_radius = self._radius - - def set_size_and_radius(self, *args) -> None: - self.set_size(args) - self.set__radius(args) - - def set_size(self, *args) -> None: - if self.theme_cls.material_style == "M2": - self.size = dp(56), dp(56) - else: - if self.type == "small": - self.size = dp(40), dp(40) - elif self.type == "standard": - self.size = dp(56), dp(56) - elif self.type == "large": - self.size = dp(96), dp(96) - - def on_type(self, instance_md_floating_action_button, type: str) -> None: - self.set_size() - self.set_font_size() - - -class MDTextButton(ButtonBehavior, MDLabel): - """ - Text button class. - - For more information, see in the - :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.label.MDLabel` classes documentation. - """ - - color = ColorProperty(None) - """ - Button color in (r, g, b, a) or string format. - - :attr:`color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_disabled = ColorProperty(None) - """ - Button color disabled in (r, g, b, a) or string format. - - :attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _color = ColorProperty(None) # last current button text color - - def animation_label(self) -> None: - def set_default_state_label(*args): - Animation(opacity=1, d=0.1, t="in_out_cubic").start(self) - - anim = Animation(opacity=0.5, d=0.2, t="in_out_cubic") - anim.bind(on_complete=set_default_state_label) - anim.start(self) - - def on_press(self, *args): - self.animation_label() - return super().on_press(*args) - - def on_disabled(self, instance_button, disabled_value) -> None: - if disabled_value: - if not self.color_disabled: - self.color_disabled = self.theme_cls.disabled_hint_text_color - self._color = self.color - self.text_color = self.color_disabled - else: - self.text_color = self._color - - -# SpeedDial classes - - -class BaseFloatingBottomButton(MDFloatingActionButton, MDTooltip): - _canvas_width = NumericProperty(0) - _padding_right = NumericProperty(0) - _bg_color = ColorProperty(None) - - def set_size(self, interval: Union[int, float]) -> None: - self.width = "46dp" - self.height = "46dp" - - -class MDFloatingBottomButton(BaseFloatingBottomButton): - _bg_color = ColorProperty(None) - - -class MDFloatingRootButton(RotateBehavior, MDFloatingActionButton): - rotate_value_angle = NumericProperty(0) - - -class MDFloatingLabel(MDLabel): - bg_color = ColorProperty([0, 0, 0, 0]) - - -class MDFloatingActionButtonSpeedDial( - DeclarativeBehavior, ThemableBehavior, FloatLayout -): - """ - For more information, see in the - :class:`~kivy.uix.floatlayout.FloatLayout` class documentation. - - For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.floatlayout.FloatLayout` - lasses documentation. - - :Events: - :attr:`on_open` - Called when a stack is opened. - :attr:`on_close` - Called when a stack is closed. - :attr:`on_press_stack_button` - Called at the on_press event for the stack button. - :attr:`on_release_stack_button` - Called at the on_press event for the stack button. - """ - - icon = StringProperty("plus") - """ - Root button icon name. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - icon: "pencil" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-icon.png - :align: center - - :attr:`icon` is a :class:`~kivy.properties.StringProperty` - and defaults to `'plus'`. - """ - - anchor = OptionProperty("right", option=["right"]) - """ - Stack anchor. Available options are: `'right'`. - - :attr:`anchor` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'right'`. - """ - - label_text_color = ColorProperty(None) - """ - Color of floating text labels in (r, g, b, a) or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - label_text_color: "orange" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-text-color.png - :align: center - - :attr:`label_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - label_bg_color = ColorProperty([0, 0, 0, 0]) - """ - Background color of floating text labels in (r, g, b, a) or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - label_text_color: "black" - label_bg_color: "orange" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-bg-color.png - :align: center - - :attr:`label_bg_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - label_radius = VariableListProperty([0], length=4) - """ - The radius of the background of floating text labels. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - label_text_color: "black" - label_bg_color: "orange" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-label-radius.png - :align: center - - :attr:`label_radius` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - data = DictProperty() - """ - Must be a dictionary. - - .. code-block:: python - - { - 'name-icon': 'Text label', - ..., - ..., - } - """ - - right_pad = BooleanProperty(False) - """ - If `True`, the background for the floating text label will increase by the - number of pixels specified in the :attr:`~right_pad_value` parameter. - - Works only if the :attr:`~hint_animation` parameter is set to `True`. - - .. rubric:: False - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - hint_animation: True - right_pad: False - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif - :align: center - - .. rubric:: True - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - hint_animation: True - right_pad: True - right_pad_value: "10dp" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif - :align: center - - :attr:`right_pad` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - right_pad_value = NumericProperty(0) - """ - See :attr:`~right_pad` parameter for more information. - - :attr:`right_pad_value` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - root_button_anim = BooleanProperty(False) - """ - If ``True`` then the root button will rotate 45 degrees when the stack - is opened. - - :attr:`root_button_anim` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - opening_transition = StringProperty("out_cubic") - """ - The name of the stack opening animation type. - - :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - closing_transition = StringProperty("out_cubic") - """ - The name of the stack closing animation type. - - :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_transition_button_rotation = StringProperty("out_cubic") - """ - The name of the animation type to rotate the root button when opening the - stack. - - :attr:`opening_transition_button_rotation` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - closing_transition_button_rotation = StringProperty("out_cubic") - """ - The name of the animation type to rotate the root button when closing the - stack. - - :attr:`closing_transition_button_rotation` is a :class:`~kivy.properties.StringProperty` - and defaults to `'out_cubic'`. - """ - - opening_time = NumericProperty(0.5) - """ - Time required for the stack to go to: attr:`state` `'open'`. - - :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_time = NumericProperty(0.2) - """ - Time required for the stack to go to: attr:`state` `'close'`. - - :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - opening_time_button_rotation = NumericProperty(0.2) - """ - Time required to rotate the root button 45 degrees during the stack - opening animation. - - :attr:`opening_time_button_rotation` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - closing_time_button_rotation = NumericProperty(0.2) - """ - Time required to rotate the root button 0 degrees during the stack - closing animation. - - :attr:`closing_time_button_rotation` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - state = OptionProperty("close", options=("close", "open")) - """ - Indicates whether the stack is closed or open. - Available options are: `'close'`, `'open'`. - - :attr:`state` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - bg_color_root_button = ColorProperty(None) - """ - Background color of root button in (r, g, b, a) or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - bg_color_root_button: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-root-button.png - :align: center - - :attr:`bg_color_root_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - bg_color_stack_button = ColorProperty(None) - """ - Background color of the stack buttons in (r, g, b, a) or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - bg_color_root_button: "red" - bg_color_stack_button: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-color-stack-button.png - :align: center - - :attr:`bg_color_stack_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_icon_stack_button = ColorProperty(None) - """ - The color icon of the stack buttons in (r, g, b, a) or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - bg_color_root_button: "red" - bg_color_stack_button: "red" - color_icon_stack_button: "white" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-stack-button.png - :align: center - - :attr:`color_icon_stack_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - color_icon_root_button = ColorProperty(None) - """ - The color icon of the root button in (r, g, b, a) or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - bg_color_root_button: "red" - bg_color_stack_button: "red" - color_icon_stack_button: "white" - color_icon_root_button: self.color_icon_stack_button - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-color-icon-root-button.png - :align: center - - :attr:`color_icon_root_button` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - bg_hint_color = ColorProperty(None) - """ - Background color for the floating text of the buttons in (r, g, b, a) - or string format. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - bg_hint_color: "red" - hint_animation: True - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-bg-hint-color.png - :align: center - - :attr:`bg_hint_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - hint_animation = BooleanProperty(False) - """ - Whether to use button extension animation to display floating text. - - :attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - stack_buttons = DictProperty() - - _label_pos_y_set = False - _anim_buttons_data = {} - _anim_labels_data = {} - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_open") - self.register_event_type("on_close") - self.register_event_type("on_press_stack_button") - self.register_event_type("on_release_stack_button") - Window.bind(on_resize=self._update_pos_buttons) - - def on_open(self, *args): - """Called when a stack is opened.""" - - def on_close(self, *args): - """Called when a stack is closed.""" - - def on_leave(self, instance_button: MDFloatingBottomButton) -> None: - """Called when the mouse cursor goes outside the button of stack.""" - - if self.state == "open": - for widget in self.children: - if isinstance(widget, MDFloatingLabel) and self.hint_animation: - Animation.cancel_all(widget) - for item in self.data.items(): - if widget.text in item: - Animation( - _canvas_width=0, - _padding_right=0, - d=self.opening_time, - t=self.opening_transition, - _elevation=0, - ).start(instance_button) - Animation( - opacity=0, d=0.1, t=self.opening_transition - ).start(widget) - - def on_enter(self, instance_button: MDFloatingBottomButton) -> None: - """Called when the mouse cursor is over a button from the stack.""" - - if self.state == "open": - for widget in self.children: - if isinstance(widget, MDFloatingLabel) and self.hint_animation: - Animation.cancel_all(widget) - for item in self.data.items(): - if widget.text in item: - Animation( - _canvas_width=widget.width + dp(24), - _padding_right=self.right_pad_value - if self.right_pad - else 0, - d=self.opening_time, - t=self.opening_transition, - ).start(instance_button) - if ( - instance_button.icon - == self.data[f"{widget.text}"] - or instance_button.icon - == self.data[f"{widget.text}"][0] - ): - Animation( - opacity=1, - d=self.opening_time, - t=self.opening_transition, - ).start(widget) - else: - Animation( - opacity=0, d=0.1, t=self.opening_transition - ).start(widget) - - def on_data(self, instance_speed_dial, data: dict) -> None: - """Creates a stack of buttons.""" - - def on_data(*args): - # Bottom buttons. - for name, parameters in data.items(): - name_icon = ( - parameters if (type(parameters) is str) else parameters[0] - ) - - bottom_button = MDFloatingBottomButton( - icon=name_icon, - on_enter=self.on_enter, - on_leave=self.on_leave, - opacity=0, - ) - bottom_button.bind( - on_press=lambda x: self.dispatch("on_press_stack_button"), - on_release=lambda x: self.dispatch( - "on_release_stack_button" - ), - ) - - if "on_press" in parameters: - callback = parameters[parameters.index("on_press") + 1] - bottom_button.bind(on_press=callback) - - if "on_release" in parameters: - callback = parameters[parameters.index("on_release") + 1] - bottom_button.bind(on_release=callback) - - self.set_pos_bottom_buttons(bottom_button) - self.add_widget(bottom_button) - self.stack_buttons[name] = WeakProxy(bottom_button) - # Labels. - floating_text = name - if floating_text: - label = MDFloatingLabel(text=floating_text, opacity=0) - label.bg_color = self.label_bg_color - label.radius = self.label_radius - label.text_color = ( - self.label_text_color - if self.label_text_color - else self.theme_cls.text_color - ) - self.add_widget(label) - # Top root button. - root_button = MDFloatingRootButton(on_release=self.open_stack) - root_button.icon = self.icon - self.set_pos_root_button(root_button) - self.add_widget(root_button) - - self.clear_widgets() - self.stack_buttons = {} - self._anim_buttons_data = {} - self._anim_labels_data = {} - self._label_pos_y_set = False - Clock.schedule_once(on_data) - - def on_icon(self, instance_speed_dial, name_icon: str) -> None: - self._set_button_property(MDFloatingRootButton, "icon", name_icon) - - def on_label_text_color( - self, instance_speed_dial, color: list | str - ) -> None: - for widget in self.children: - if isinstance(widget, MDFloatingLabel): - widget.text_color = color - - def on_color_icon_stack_button( - self, instance_speed_dial, color: list - ) -> None: - self._set_button_property(MDFloatingBottomButton, "icon_color", color) - - def on_hint_animation(self, instance_speed_dial, value: bool) -> None: - for widget in self.children: - if isinstance(widget, MDFloatingLabel): - widget.md_bg_color = (0, 0, 0, 0) - - def on_bg_hint_color(self, instance_speed_dial, color: list) -> None: - setattr(MDFloatingBottomButton, "_bg_color", color) - - def on_color_icon_root_button( - self, instance_speed_dial, color: list - ) -> None: - self._set_button_property(MDFloatingRootButton, "icon_color", color) - - def on_bg_color_stack_button( - self, instance_speed_dial, color: list - ) -> None: - self._set_button_property(MDFloatingBottomButton, "md_bg_color", color) - - def on_bg_color_root_button(self, instance_speed_dial, color: list) -> None: - self._set_button_property(MDFloatingRootButton, "md_bg_color", color) - - def on_press_stack_button(self, *args) -> None: - """ - Called at the on_press event for the stack button. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - on_press_stack_button: print(*args) - - .. versionadded:: 1.1.0 - """ - - def on_release_stack_button(self, *args) -> None: - """ - Called at the on_release event for the stack button. - - .. code-block:: kv - - MDFloatingActionButtonSpeedDial: - on_release_stack_button: print(*args) - - .. versionadded:: 1.1.0 - """ - - def set_pos_labels(self, instance_floating_label: MDFloatingLabel) -> None: - """ - Sets the position of the floating labels. - Called when the application's root window is resized. - """ - - if self.anchor == "right": - instance_floating_label.x = ( - Window.width - instance_floating_label.width - dp(86) - ) - - def set_pos_root_button( - self, instance_floating_root_button: MDFloatingRootButton - ) -> None: - """ - Sets the position of the root button. - Called when the application's root window is resized. - """ - - def set_pos_root_button(*args): - if self.anchor == "right": - instance_floating_root_button.y = dp(20) - instance_floating_root_button.x = self.parent.width - ( - dp(56) + dp(20) - ) - - Clock.schedule_once(set_pos_root_button) - - def set_pos_bottom_buttons( - self, instance_floating_bottom_button: MDFloatingBottomButton - ) -> None: - """ - Sets the position of the bottom buttons in a stack. - Called when the application's root window is resized. - """ - - if self.anchor == "right": - if self.state != "open": - instance_floating_bottom_button.y = ( - instance_floating_bottom_button.height / 2 - ) - instance_floating_bottom_button.x = Window.width - ( - instance_floating_bottom_button.height - + instance_floating_bottom_button.width / 2 - ) - - def open_stack( - self, instance_floating_root_button: MDFloatingRootButton - ) -> None: - """Opens a button stack.""" - - for widget in self.children: - if isinstance(widget, MDFloatingLabel): - Animation.cancel_all(widget) - - if self.state != "open": - y = 0 - label_position = dp(54) - anim_buttons_data = {} - anim_labels_data = {} - - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - # Sets new button positions. - y += dp(56) - widget.y = widget.y * 2 + y - if not self._anim_buttons_data: - anim_buttons_data[widget] = Animation( - opacity=1, - d=self.opening_time, - t=self.opening_transition, - ) - elif isinstance(widget, MDFloatingLabel): - # Sets new labels positions. - label_position += dp(56) - # Sets the position of signatures only once. - if not self._label_pos_y_set: - widget.y = widget.y * 2 + label_position - widget.x = Window.width - widget.width - dp(86) - if not self._anim_labels_data: - anim_labels_data[widget] = Animation( - opacity=1, d=self.opening_time - ) - elif ( - isinstance(widget, MDFloatingRootButton) - and self.root_button_anim - ): - # Rotates the root button 45 degrees. - Animation( - rotate_value_angle=-45, - d=self.opening_time_button_rotation, - t=self.opening_transition_button_rotation, - ).start(widget) - - if anim_buttons_data: - self._anim_buttons_data = anim_buttons_data - if anim_labels_data and not self.hint_animation: - self._anim_labels_data = anim_labels_data - - self.state = "open" - self.dispatch("on_open") - self.do_animation_open_stack(self._anim_buttons_data) - self.do_animation_open_stack(self._anim_labels_data) - if not self._label_pos_y_set: - self._label_pos_y_set = True - else: - self.close_stack() - - def do_animation_open_stack(self, anim_data: dict) -> None: - """ - :param anim_data: - { - : - , - : - , - ..., - } - """ - - def on_progress(animation, widget, value): - if value >= 0.1: - animation_open_stack() - - def animation_open_stack(*args): - try: - widget = next(widgets_list) - animation = anim_data[widget] - animation.bind(on_progress=on_progress) - animation.start(widget) - except StopIteration: - pass - - widgets_list = iter(list(anim_data.keys())) - animation_open_stack() - - def close_stack(self): - """Closes the button stack.""" - - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - Animation( - y=widget.height / 2, - d=self.closing_time, - t=self.closing_transition, - opacity=0, - ).start(widget) - elif isinstance(widget, MDFloatingLabel): - if widget.opacity > 0: - Animation(opacity=0, d=0.1).start(widget) - elif ( - isinstance(widget, MDFloatingRootButton) - and self.root_button_anim - ): - Animation( - rotate_value_angle=0, - d=self.closing_time_button_rotation, - t=self.closing_transition_button_rotation, - ).start(widget) - self.state = "close" - self.dispatch("on_close") - - def _update_pos_buttons(self, instance, width, height): - # Updates button positions when resizing screen. - for widget in self.children: - if isinstance(widget, MDFloatingBottomButton): - self.set_pos_bottom_buttons(widget) - elif isinstance(widget, MDFloatingRootButton): - self.set_pos_root_button(widget) - elif isinstance(widget, MDFloatingLabel): - self.set_pos_labels(widget) - - def _set_button_property( - self, instance, property_name: str, property_value: str | list - ): - def set_count_widget(*args): - if self.children: - for widget in self.children: - if isinstance(widget, instance): - setattr(instance, property_name, property_value) - Clock.unschedule(set_count_widget) - break - - Clock.schedule_interval(set_count_widget, 0) + self.register_event_type("on_collapse") + self.register_event_type("on_expand") + Clock.schedule_once(self._set_text_pos, 0.5) + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDExtendedFabButtonIcon): + self._icon = widget + elif isinstance(widget, MDExtendedFabButtonText): + self._label = widget + widget.opacity = 0 + + return super().add_widget(widget) + + def on_collapse(self, *args): + """Fired when the button is collapsed.""" + + def on_expand(self, *args): + """Fired when the button is expanded.""" + + def on_fab_state(self, instance, state: str) -> None: + """Fired when the :attr:`fab_state` value changes.""" + + if state == "expand": + Clock.schedule_once(self.expand) + Clock.schedule_once(lambda x: self.dispatch("on_expand")) + elif state == "collapse": + Clock.schedule_once(self.collapse) + Clock.schedule_once(lambda x: self.dispatch("on_collapse")) + + def on__x(self, instance, value) -> None: + self._label.x = ( + self._icon.x + self._icon.texture_size[0] + dp(24) - value + ) + + def _set_text_pos(self, *args): + if self._icon and self._label: + self._label.x = self._icon.x + self._icon.texture_size[0] + dp(24) + elif not self._icon and self._label: + self._label.opacity = 1 + self.width = self._label.texture_size[0] + dp(32) + self._label.pos_hint = {"center_x": 0.5} diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__init__.py index 7577fc9..9bcb032 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__init__.py @@ -4,5 +4,4 @@ from .card import ( MDCardSwipe, MDCardSwipeFrontBox, MDCardSwipeLayerBox, - MDSeparator, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/__init__.cpython-311.pyc index f46eaa7..b62f8c4 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/card.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/card.cpython-311.pyc index f112a5a..326f147 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/card.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/__pycache__/card.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.kv index 7ed37bd..26d2a37 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.kv @@ -1,9 +1,95 @@ : - md_bg_color: app.theme_cls.divider_color - - - md_bg_color: - self.theme_cls.divider_color \ - if not root.color \ - else root.color + app.theme_cls.secondaryContainerColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color + + + + shadow_radius: [(self.radius[0] / 2) + dp(2), ] + shadow_color: + { \ + "filled": self.theme_cls.transparentColor, \ + "outlined": self.theme_cls.transparentColor, \ + "elevated": self.theme_cls.shadowColor \ + if self.theme_shadow_color == "Primary" else \ + self.shadow_color, \ + }[self.style] \ + if not self.disabled else \ + { \ + "filled": self.theme_cls.transparentColor, \ + "outlined": self.theme_cls.transparentColor, \ + "elevated": self.theme_cls.shadowColor[:-1] + [.5] \ + if self.theme_shadow_color == "Primary" else \ + self.shadow_color[:-1] + [.5], + }[self.style] + shadow_offset: + ( \ + { \ + "filled": [0, 0], \ + "outlined": [0, 0], \ + "elevated": [0, -2], \ + }[self.style] \ + if not self.disabled else \ + { \ + "filled": [0, 0], \ + "outlined": [0, 0], \ + "elevated": [0, -2], \ + }[self.style] \ + ) \ + if self.theme_shadow_offset == "Primary" else self.shadow_offset + shadow_softness: + ( \ + { \ + "filled": 0, \ + "outlined": 0, \ + "elevated": dp(4), \ + }[self.style] \ + if not self.disabled else \ + { \ + "filled": 0, \ + "outlined": 0, \ + "elevated": dp(4), \ + }[self.style] \ + ) \ + if self.theme_shadow_softness == "Primary" else self.shadow_softness + elevation_level: + ( \ + ( \ + { \ + "filled": 0, \ + "outlined": 0, \ + "elevated": self.elevation_level if self.elevation_level else 1, \ + }[self.style] \ + ) \ + if self.theme_elevation_level == "Primary" else self.elevation_level \ + ) \ + if not self.disabled else \ + { \ + "filled": 1, \ + "outlined": 0, \ + "elevated": self.elevation_level if self.elevation_level else 1, \ + }[self.style] + elevation: self.elevation_levels[self.elevation_level] + line_color: + (\ + ( \ + self.theme_cls.outlineColor \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + \ + [self.button_outlined_opacity_value_disabled_line] \ + ) \ + if self.style == "outlined" else \ + self.theme_cls.transparentColor \ + ) \ + if self.theme_line_color == "Primary" else self.line_color + md_bg_color: + ( \ + { \ + "filled": app.theme_cls.surfaceContainerHighestColor, \ + "outlined": app.theme_cls.surfaceColor, \ + "elevated": app.theme_cls.surfaceContainerLowColor, \ + }[self.style] \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color \ + ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.py index 7e0bdf9..5ecfc8f 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/card/card.py @@ -4,8 +4,7 @@ Components/Card .. seealso:: - `Material Design spec, Cards `_ and - `Material Design 3 spec, Cards `_ + `Material Design 3 spec, Cards `_ .. rubric:: Cards contain content and actions about a single subject. @@ -23,11 +22,21 @@ Components/Card :class:`~MDCard` class. .. MDCard: + MDCard ------ -An example of the implementation of a card in the style of material design version 3 ------------------------------------------------------------------------------------- +There are three types of cards: elevated, filled, and outlined: + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/available-cards.png + :align: center + +1. Elevated card +2. Filled card +3. Outlined card + +Example +------- .. tabs:: @@ -42,10 +51,10 @@ An example of the implementation of a card in the style of material design versi from kivymd.uix.card import MDCard KV = ''' - - padding: 4 + + padding: "4dp" size_hint: None, None - size: "200dp", "100dp" + size: "240dp", "100dp" MDRelativeLayout: @@ -54,7 +63,6 @@ An example of the implementation of a card in the style of material design versi pos_hint: {"top": 1, "right": 1} MDLabel: - id: label text: root.text adaptive_size: True color: "grey" @@ -63,39 +71,31 @@ An example of the implementation of a card in the style of material design versi MDScreen: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.backgroundColor MDBoxLayout: id: box adaptive_size: True - spacing: "56dp" + spacing: "12dp" pos_hint: {"center_x": .5, "center_y": .5} ''' - class MD3Card(MDCard): - '''Implements a material design v3 card.''' + class MyCard(MDCard): + '''Implements a material card.''' text = StringProperty() class Example(MDApp): def build(self): - self.theme_cls.material_style = "M3" return Builder.load_string(KV) def on_start(self): - styles = { - "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4" - } - for style in styles.keys(): + for style in ("elevated", "filled", "outlined"): self.root.ids.box.add_widget( - MD3Card( - line_color=(0.2, 0.2, 0.2, 0.8), - style=style, - text=style.capitalize(), - md_bg_color=styles[style], - shadow_offset=(0, -1), - ) + MyCard(style=style, text=style.capitalize()) ) @@ -114,31 +114,29 @@ An example of the implementation of a card in the style of material design versi from kivymd.uix.screen import MDScreen - class MD3Card(MDCard): - '''Implements a material design v3 card.''' + class MyCard(MDCard): + '''Implements a material card.''' class Example(MDApp): def build(self): - self.theme_cls.material_style = "M3" return ( MDScreen( MDBoxLayout( id="box", adaptive_size=True, - spacing="56dp", + spacing="12dp", pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) + ), + theme_bg_color="Custom", + md_bg_color=self.theme_cls.backgroundColor, ) ) def on_start(self): - styles = { - "elevated": "#f6eeee", "filled": "#f4dedc", "outlined": "#f8f5f4" - } - for style in styles.keys(): + for style in ("elevated", "filled", "outlined"): self.root.ids.box.add_widget( - MD3Card( + MyCard( MDRelativeLayout( MDIconButton( icon="dots-vertical", @@ -147,25 +145,118 @@ An example of the implementation of a card in the style of material design versi MDLabel( text=style.capitalize(), adaptive_size=True, - color="grey", pos=("12dp", "12dp"), ), ), - line_color=(0.2, 0.2, 0.2, 0.8), style=style, - text=style.capitalize(), - md_bg_color=styles[style], - shadow_offset=(0, -1), + padding="4dp", + size_hint=(None, None), + size=("240dp", "100dp"), + ripple_behavior=True, ) ) Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/cards-m3.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-usage.png + :align: center + +Elevated +-------- + +.. code-block:: kv + + MDCard + style: "elevated" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-elevated.png + :align: center + +Filled +------ + +.. code-block:: kv + + MDCard + style: "filled" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-filled.png + :align: center + +Outlined +-------- + +.. code-block:: kv + + MDCard + style: "outlined" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-outlined.png + :align: center + + +Customization of card +===================== + +.. code-block:: kv + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.backgroundColor + + MDCard: + style: "elevated" + pos_hint: {"center_x": .5, "center_y": .5} + padding: "4dp" + size_hint: None, None + size: "240dp", "100dp" + # Sets custom properties. + theme_shadow_color: "Custom" + shadow_color: "green" + theme_bg_color: "Custom" + md_bg_color: "white" + md_bg_color_disabled: "grey" + theme_shadow_offset: "Custom" + shadow_offset: (1, -2) + theme_shadow_softness: "Custom" + shadow_softness: 1 + theme_elevation_level: "Custom" + elevation_level: 2 + + MDRelativeLayout: + + MDIconButton: + icon: "dots-vertical" + pos_hint: {"top": 1, "right": 1} + + MDLabel: + text: "Elevated" + adaptive_size: True + color: "grey" + pos: "12dp", "12dp" + bold: True + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Green" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-customization.png :align: center .. MDCardSwipe: + MDCardSwipe ----------- @@ -196,8 +287,8 @@ that inherits from the :class:`~MDCardSwipe` class: .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sceleton-mdcard-swiper.png :align: center -End full code -------------- +Example +------- .. tabs:: @@ -212,16 +303,20 @@ End full code from kivymd.uix.card import MDCardSwipe KV = ''' - + : size_hint_y: None height: content.height MDCardSwipeLayerBox: - # Content under the card. + padding: "8dp" + + MDIconButton: + icon: "trash-can" + pos_hint: {"center_y": .5} + on_release: app.remove_item(root) MDCardSwipeFrontBox: - # Content of card. OneLineListItem: id: content text: root.text @@ -230,39 +325,34 @@ End full code MDScreen: - MDBoxLayout: - orientation: "vertical" + MDScrollView: - MDTopAppBar: - elevation: 4 - title: "MDCardSwipe" - - MDScrollView: - scroll_timeout : 100 - - MDList: - id: md_list - padding: 0 + MDList: + id: md_list + padding: 0 ''' class SwipeToDeleteItem(MDCardSwipe): - '''Card with `swipe-to-delete` behavior.''' - text = StringProperty() class Example(MDApp): - def build(self): + def __init__(self, **kwargs): + super().__init__(**kwargs) self.theme_cls.theme_style = "Dark" self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) + self.screen = Builder.load_string(KV) + + def build(self): + return self.screen + + def remove_item(self, instance): + self.screen.ids.md_list.remove_widget(instance) def on_start(self): - '''Creates a list of cards.''' - for i in range(20): - self.root.ids.md_list.add_widget( + self.screen.ids.md_list.add_widget( SwipeToDeleteItem(text=f"One-line item {i}") ) @@ -274,14 +364,12 @@ End full code .. code-block:: python from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.card import ( MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox ) from kivymd.uix.list import MDList, OneLineListItem from kivymd.uix.screen import MDScreen from kivymd.uix.scrollview import MDScrollView - from kivymd.uix.toolbar import MDTopAppBar class Example(MDApp): @@ -290,20 +378,13 @@ End full code self.theme_cls.primary_palette = "Orange" return ( MDScreen( - MDBoxLayout( - MDTopAppBar( - elevation=4, - title="MDCardSwipe", + MDScrollView( + MDList( + id="md_list", + padding=0, ), - MDScrollView( - MDList( - id="md_list", - ), - id="scroll", - scroll_timeout=100, - ), - id="box", - orientation="vertical", + id="scroll", + scroll_timeout=100, ), ) ) @@ -312,7 +393,7 @@ End full code '''Creates a list of cards.''' for i in range(20): - self.root.ids.box.ids.scroll.ids.md_list.add_widget( + self.root.ids.scroll.ids.md_list.add_widget( MDCardSwipe( MDCardSwipeLayerBox(), MDCardSwipeFrontBox( @@ -323,7 +404,7 @@ End full code ) ), size_hint_y=None, - height="52dp", + height="48dp", ) ) @@ -355,19 +436,11 @@ Swipe behavior # By default, the parameter is "hand" - type_swipe: "hand" + type_swipe: "hand" # "auto" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hand-mdcard-swipe.gif :align: center -.. code-block:: kv - - : - type_swipe: "auto" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif - :align: center - Removing an item using the ``type_swipe = "auto"`` parameter ------------------------------------------------------------ @@ -410,9 +483,10 @@ You can use this event to remove items from a list: def on_swipe_complete(self, instance): self.root.ids.box.ids.scroll.ids.md_list.remove_widget(instance) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/autodelete-mdcard-swipe.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/auto-mdcard-swipe.gif :align: center + Add content to the bottom layer of the card ------------------------------------------- @@ -431,256 +505,20 @@ use the :class:`~MDCardSwipeLayerBox` class. pos_hint: {"center_y": .5} on_release: app.remove_item(root) -End full code -------------- - -.. tabs:: - - .. tab:: Declarative KV styles - - .. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.card import MDCardSwipe - - KV = ''' - : - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - icon: "trash-can" - pos_hint: {"center_y": .5} - on_release: app.remove_item(root) - - MDCardSwipeFrontBox: - - OneLineListItem: - id: content - text: root.text - _no_ripple_effect: True - - - MDScreen: - - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - elevation: 4 - title: "MDCardSwipe" - - MDScrollView: - - MDList: - id: md_list - padding: 0 - ''' - - - class SwipeToDeleteItem(MDCardSwipe): - text = StringProperty() - - - class Example(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - self.screen = Builder.load_string(KV) - - def build(self): - return self.screen - - def remove_item(self, instance): - self.screen.ids.md_list.remove_widget(instance) - - def on_start(self): - for i in range(20): - self.screen.ids.md_list.add_widget( - SwipeToDeleteItem(text=f"One-line item {i}") - ) - - - Example().run() - - .. tab:: Decralative python styles - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDIconButton - from kivymd.uix.card import ( - MDCardSwipe, MDCardSwipeLayerBox, MDCardSwipeFrontBox - ) - from kivymd.uix.list import MDList, OneLineListItem - from kivymd.uix.screen import MDScreen - from kivymd.uix.scrollview import MDScrollView - from kivymd.uix.toolbar import MDTopAppBar - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDBoxLayout( - MDTopAppBar( - elevation=4, - title="MDCardSwipe", - ), - MDScrollView( - MDList( - id="md_list", - ), - id="scroll", - scroll_timeout=100, - ), - id="box", - orientation="vertical", - ), - ) - ) - - def on_start(self): - '''Creates a list of cards.''' - - for i in range(20): - self.root.ids.box.ids.scroll.ids.md_list.add_widget( - MDCardSwipe( - MDCardSwipeLayerBox( - MDIconButton( - icon="trash-can", - pos_hint={"center_y": 0.5}, - on_release=self.remove_item, - ), - ), - MDCardSwipeFrontBox( - OneLineListItem( - id="content", - text=f"One-line item {i}", - _no_ripple_effect=True, - ) - ), - size_hint_y=None, - height="52dp", - ) - ) - - def remove_item(self, instance): - self.root.ids.box.ids.scroll.ids.md_list.remove_widget( - instance.parent.parent - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/handdelete-mdcard-swipe.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdcard-swipe-content.png :align: center - -Focus behavior --------------- - -.. code-block:: kv - - MDCard: - focus_behavior: True - -.. tabs:: - - .. tab:: Declarative KV styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDCard: - size_hint: .7, .4 - focus_behavior: True - pos_hint: {"center_x": .5, "center_y": .5} - md_bg_color: "darkgrey" - unfocus_color: "darkgrey" - focus_color: "grey" - elevation: 6 - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - - .. tab:: Declarative python styles - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.card import MDCard - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return ( - MDScreen( - MDCard( - size_hint=(0.7, 0.4), - focus_behavior=True, - pos_hint={"center_x": 0.5, "center_y": 0.5}, - md_bg_color="darkgrey", - unfocus_color="darkgrey", - focus_color="grey", - elevation=6, - ), - ) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-focus.gif - :align: center - -Ripple behavior ---------------- - -.. code-block:: kv - - MDCard: - ripple_behavior: True - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/card-behavior.gif - :align: center - """ +from __future__ import annotations + __all__ = ( "MDCard", "MDCardSwipe", "MDCardSwipeFrontBox", "MDCardSwipeLayerBox", - "MDSeparator", ) import os -from typing import Union from kivy.animation import Animation from kivy.clock import Clock @@ -688,67 +526,32 @@ from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( BooleanProperty, - ColorProperty, NumericProperty, OptionProperty, StringProperty, VariableListProperty, + ColorProperty, ) +from kivy.uix.behaviors import ButtonBehavior from kivy.uix.boxlayout import BoxLayout -from kivy.utils import get_color_from_hex from kivymd import uix_path -from kivymd.color_definitions import colors -from kivymd.material_resources import ( - CARD_STYLE_ELEVATED_M3_ELEVATION, - CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION, -) from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget from kivymd.uix.behaviors import ( - BackgroundColorBehavior, CommonElevationBehavior, DeclarativeBehavior, RectangularRippleBehavior, + BackgroundColorBehavior, ) -from kivymd.uix.behaviors.focus_behavior import FocusBehavior +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.relativelayout import MDRelativeLayout with open( os.path.join(uix_path, "card", "card.kv"), encoding="utf-8" ) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDSeparator(MDBoxLayout): - """ - A separator line. - - For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. - """ - - color = ColorProperty(None) - """ - Separator color in (r, g, b, a) or string format. - - :attr:`color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.on_orientation() - - def on_orientation(self, *args) -> None: - self.size_hint = ( - (1, None) if self.orientation == "horizontal" else (None, 1) - ) - if self.orientation == "horizontal": - self.height = dp(1) - else: - self.width = dp(1) + Builder.load_string(kv_file.read(), filename="MDCard.kv") class MDCard( @@ -756,34 +559,28 @@ class MDCard( MDAdaptiveWidget, ThemableBehavior, BackgroundColorBehavior, - RectangularRippleBehavior, CommonElevationBehavior, - FocusBehavior, + RectangularRippleBehavior, + StateLayerBehavior, + ButtonBehavior, BoxLayout, ): """ Card class. For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and :class:`~kivymd.uix.MDAdaptiveWidget` and :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivymd.uix.behaviors.BackgroundColorBehavior` and - :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and - :class:`~kivymd.uix.behaviors.CommonElevationBehavior` and - :class:`~kivymd.uix.behaviors.FocusBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and :class:`~kivy.uix.boxlayout.BoxLayout` and classes documentation. """ - focus_behavior = BooleanProperty(False) - """ - Using focus when hovering over a card. - - :attr:`focus_behavior` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - ripple_behavior = BooleanProperty(False) """ Use ripple effect for card. @@ -792,17 +589,17 @@ class MDCard( and defaults to `False`. """ - radius = VariableListProperty([dp(6), dp(6), dp(6), dp(6)]) + radius = VariableListProperty([dp(16), dp(16), dp(16), dp(16)]) """ Card radius by default. .. versionadded:: 1.0.0 :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[dp(6), dp(6), dp(6), dp(6)]`. + and defaults to `[dp(16), dp(16), dp(16), dp(16)]`. """ - style = OptionProperty(None, options=("filled", "elevated", "outlined")) + style = OptionProperty("filled", options=("filled", "elevated", "outlined")) """ Card type. @@ -811,65 +608,78 @@ class MDCard( Available options are: 'filled', 'elevated', 'outlined'. :attr:`style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'elevated'`. + and defaults to `None`. """ - _bg_color_map = ( - get_color_from_hex(colors["Light"]["CardsDialogs"]), - get_color_from_hex(colors["Dark"]["CardsDialogs"]), - [1.0, 1.0, 1.0, 0.0], - ) + md_bg_color_disabled = ColorProperty(None) + """ + The background color in (r, g, b, a) or string format of the card when + the card is disabled. + + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.theme_cls.bind( - material_style=self.set_style, theme_style=self.update_md_bg_color - ) - Clock.schedule_once(self.set_style) Clock.schedule_once( lambda x: self.on_ripple_behavior(0, self.ripple_behavior) ) - self.update_md_bg_color(self, self.theme_cls.theme_style) - def update_md_bg_color(self, instance_card, theme_style: str) -> None: - if self.md_bg_color in self._bg_color_map: - self.md_bg_color = get_color_from_hex( - colors[theme_style]["CardsDialogs"] - ) + def on_press(self, *args) -> None: + """Fired when the button is pressed.""" - def set_style(self, *args) -> None: - self.set_radius() - self.set_elevation() - self.set_line_color() + self._on_press(args) - def set_line_color(self) -> None: - if self.theme_cls.material_style == "M3": - if self.style == "elevated" or self.style == "filled": - self.line_color = [0, 0, 0, 0] + def on_release(self, *args) -> None: + """ + Fired when the button is released + (i.e. the touch/click that pressed the button goes away). + """ - def set_elevation(self) -> None: - if self.theme_cls.material_style == "M3": - if self.style == "outlined" or self.style == "filled": - self.elevation = CARD_STYLE_OUTLINED_FILLED_M3_ELEVATION - elif self.style == "elevated": - self.elevation = CARD_STYLE_ELEVATED_M3_ELEVATION + self._on_release(args) - def set_radius(self) -> None: - if ( - self.radius == [dp(6), dp(6), dp(6), dp(6)] - and self.theme_cls.material_style == "M3" - ): - self.radius = [dp(16), dp(16), dp(16), dp(16)] - elif ( - self.radius == [dp(16), dp(16), dp(16), dp(16)] - and self.theme_cls.material_style == "M2" - ): - self.radius = [dp(6), dp(6), dp(6), dp(6)] + def on_ripple_behavior(self, interval: int | float, value: bool) -> None: + """Fired when the :attr:`ripple_behavior` value changes.""" - def on_ripple_behavior( - self, interval: Union[int, float], value_behavior: bool - ) -> None: - self._no_ripple_effect = False if value_behavior else True + self.ripple_effect = not self.ripple_effect + + def set_properties_widget(self) -> None: + """Fired `on_release/on_press/on_enter/on_leave` events.""" + + super().set_properties_widget() + + if not self.disabled: + if self._state == self.state_hover and self.focus_behavior: + self._elevation_level = self.elevation_level + self._shadow_softness = self.shadow_softness + self._bg_color = self.md_bg_color + + if self.style in ["filled", "outlined"]: + if self.theme_elevation_level == "Primary": + self.elevation_level = 0 + if self.theme_shadow_softness == "Primary": + self.shadow_softness = 0 + else: + if self.theme_elevation_level == "Primary": + self.elevation_level = 2 + if self.theme_shadow_softness == "Primary": + self.shadow_softness = dp(4) + if self.theme_shadow_offset == "Primary": + self.shadow_offset = [0, -2] + elif self._state == self.state_press: + if self.theme_elevation_level == "Primary": + self.elevation_level = 1 + if self.theme_shadow_softness == "Primary": + self.shadow_softness = 0 + elif not self._state: + if self.theme_elevation_level == "Primary": + self.elevation_level = 1 + if self.theme_shadow_softness == "Primary": + self.shadow_softness = 0 + if self.theme_shadow_offset == "Primary": + self.shadow_offset = [0, -2] + self.md_bg_color = self._bg_color class MDCardSwipe(MDRelativeLayout): @@ -881,7 +691,7 @@ class MDCardSwipe(MDRelativeLayout): :Events: :attr:`on_swipe_complete` - Called when a swipe of card is completed. + Fired when a swipe of card is completed. """ open_progress = NumericProperty(0.0) @@ -993,16 +803,14 @@ class MDCardSwipe(MDRelativeLayout): self.register_event_type("on_swipe_complete") super().__init__(*args, **kwargs) - def add_widget(self, widget, index=0, canvas=None): - if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)): - return super().add_widget(widget) - def on_swipe_complete(self, *args): - """Called when a swipe of card is completed.""" + """Fired when a swipe of card is completed.""" def on_anchor( self, instance_swipe_to_delete_item, anchor_value: str ) -> None: + """Fired when the value of :attr:`anchor` changes.""" + if anchor_value == "right": self.open_progress = 1.0 else: @@ -1011,6 +819,8 @@ class MDCardSwipe(MDRelativeLayout): def on_open_progress( self, instance_swipe_to_delete_item, progress_value: float ) -> None: + """Fired when the value of :attr:`open_progress` changes.""" + def on_open_progress(*args): if self.anchor == "left": self.children[0].x = self.width * progress_value @@ -1043,7 +853,7 @@ class MDCardSwipe(MDRelativeLayout): if self.collide_point(touch.x, touch.y): if not self._to_closed: self._opens_process = False - self.complete_swipe() + self._complete_swipe() return super().on_touch_up(touch) def on_touch_down(self, touch): @@ -1053,18 +863,9 @@ class MDCardSwipe(MDRelativeLayout): Clock.schedule_once(self.close_card, self.closing_interval) return super().on_touch_down(touch) - def complete_swipe(self) -> None: - expr = ( - self.open_progress <= self.max_swipe_x - if self.anchor == "left" - else self.open_progress >= self.max_swipe_x - ) - if expr: - Clock.schedule_once(self.close_card, self.closing_interval) - else: - self.open_card() - def open_card(self) -> None: + """Animates the opening of the card.""" + if self.type_swipe == "hand": swipe_x = ( self.max_opened_x @@ -1081,11 +882,28 @@ class MDCardSwipe(MDRelativeLayout): self.state = "opened" def close_card(self, *args) -> None: + """Animates the closing of the card.""" + anim = Animation(x=0, t=self.closing_transition, d=self.opening_time) anim.bind(on_complete=self._reset_open_progress) anim.start(self.children[0]) self.state = "closed" + def add_widget(self, widget, index=0, canvas=None): + if isinstance(widget, (MDCardSwipeFrontBox, MDCardSwipeLayerBox)): + return super().add_widget(widget) + + def _complete_swipe(self) -> None: + expr = ( + self.open_progress <= self.max_swipe_x + if self.anchor == "left" + else self.open_progress >= self.max_swipe_x + ) + if expr: + Clock.schedule_once(self.close_card, self.closing_interval) + else: + self.open_card() + def _on_swipe_complete(self, *args): self.dispatch("on_swipe_complete") @@ -1104,4 +922,9 @@ class MDCardSwipeFrontBox(MDCard): class MDCardSwipeLayerBox(MDBoxLayout): - pass + """ + Card swipe back box. + + For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` + class documentation. + """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/carousel.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/carousel.py deleted file mode 100644 index 5f68886..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/carousel.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Components/Carousel -=================== - -:class:`~kivy.uix.boxlayout.Carousel` class equivalent. Simplifies working -with some widget properties. For example: - - -Carousel ---------- - -.. code-block:: python - - kv=''' - YourCarousel: - BoxLayout: - [...] - BoxLayout: - [...] - BoxLayout: - [...] - ''' - builder.load_string(kv) - - class YourCarousel(Carousel): - def __init__(self,*kwargs): - self.register_event_type("on_slide_progress") - self.register_event_type("on_slide_complete") - - def on_touch_down(self, *args): - ["Code to detect when the slide changes"] - - def on_touch_up(self, *args): - ["Code to detect when the slide changes"] - - def Calculate_slide_pos(self, *args): - ["Code to calculate the current position of the slide"] - - def do_custom_animation(self, *args): - ["Code to recreate an animation"] - - -MDCarousel ------------ - -.. code-block:: kv - - MDCarousel: - on_slide_progress: - do_something() - on_slide_complete: - do_something() - -""" -# TODO: Add documentation. - -from kivy.animation import Animation -from kivy.uix.carousel import Carousel - -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import DeclarativeBehavior - - -class MDCarousel(DeclarativeBehavior, ThemableBehavior, Carousel): - """ - based on kivy's carousel. - - .. seealso:: - `kivy.uix.carousel.Carousel `_ - """ - - _scrolling = False - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_event_type("on_slide_progress") - self.register_event_type("on_slide_complete") - - def on_slide_progress(self, *args): - """ - Event launched when the Slide animation is progress. - remember to bind and unbid to this method. - """ - - def on_slide_complete(self, *args): - """ - Event launched when the Slide animation is complete. - remember to bind and unbid to this method. - """ - - def _position_visible_slides(self, *args): - slides, index = self.slides, self.index - no_of_slides = len(slides) - 1 - if not slides: - return - x, y, width, height = self.x, self.y, self.width, self.height - _offset, direction = self._offset, self.direction - _prev, _next, _current = self._prev, self._next, self._current - get_slide_container = self.get_slide_container - last_slide = get_slide_container(slides[-1]) - first_slide = get_slide_container(slides[0]) - skip_next = False - _loop = self.loop - - if direction[0] in ["r", "l"]: - xoff = x + _offset - x_prev = {"l": xoff + width, "r": xoff - width} - x_next = {"l": xoff - width, "r": xoff + width} - if _prev: - _prev.pos = (x_prev[direction[0]], y) - elif _loop and _next and index == 0: - if (_offset > 0 and direction[0] == "r") or ( - _offset < 0 and direction[0] == "l" - ): - last_slide.pos = (x_prev[direction[0]], y) - skip_next = True - if _current: - _current.pos = (xoff, y) - - if self._scrolling: - self.dispatch("on_slide_progress", xoff) - - if skip_next: - return - if _next: - _next.pos = (x_next[direction[0]], y) - elif _loop and _prev and index == no_of_slides: - if (_offset < 0 and direction[0] == "r") or ( - _offset > 0 and direction[0] == "l" - ): - first_slide.pos = (x_next[direction[0]], y) - if direction[0] in ["t", "b"]: - yoff = y + _offset - y_prev = {"t": yoff - height, "b": yoff + height} - y_next = {"t": yoff + height, "b": yoff - height} - if _prev: - _prev.pos = (x, y_prev[direction[0]]) - elif _loop and _next and index == 0: - if (_offset > 0 and direction[0] == "t") or ( - _offset < 0 and direction[0] == "b" - ): - last_slide.pos = (x, y_prev[direction[0]]) - skip_next = True - if _current: - _current.pos = (x, yoff) - if skip_next: - return - if _next: - _next.pos = (x, y_next[direction[0]]) - elif _loop and _prev and index == no_of_slides: - if (_offset < 0 and direction[0] == "t") or ( - _offset > 0 and direction[0] == "b" - ): - first_slide.pos = (x, y_next[direction[0]]) - - def on_touch_down(self, touch): - self._scrolling = True - return super().on_touch_down(touch) - - def on_touch_up(self, touch): - self._scrolling = False - return super().on_touch_up(touch) - - def _start_animation(self, *args, **kwargs): - # compute target offset for ease back, next or prev - new_offset = 0 - direction = kwargs.get("direction", self.direction)[0] - is_horizontal = direction in "rl" - extent = self.width if is_horizontal else self.height - min_move = kwargs.get("min_move", self.min_move) - _offset = kwargs.get("offset", self._offset) - - if _offset < min_move * -extent: - new_offset = -extent - elif _offset > min_move * extent: - new_offset = extent - - # if new_offset is 0, it wasnt enough to go next/prev - dur = self.anim_move_duration - if new_offset == 0: - dur = self.anim_cancel_duration - - # detect edge cases if not looping - len_slides = len(self.slides) - index = self.index - if not self.loop or len_slides == 1: - is_first = index == 0 - is_last = index == len_slides - 1 - if direction in "rt": - towards_prev = new_offset > 0 - towards_next = new_offset < 0 - else: - towards_prev = new_offset < 0 - towards_next = new_offset > 0 - if (is_first and towards_prev) or (is_last and towards_next): - new_offset = 0 - - anim = Animation(_offset=new_offset, d=dur, t=self.anim_type) - anim.cancel_all(self) - - def _cmp(*args): - self.dispatch( - "on_slide_complete", - self.previous_slide, - self.current_slide, - self.next_slide, - ) - if self._skip_slide is not None: - self.index = self._skip_slide - self._skip_slide = None - - anim.bind( - on_complete=_cmp, - on_progress=lambda *args: self.dispatch( - "on_slide_progress", self._offset - ), - ) - anim.start(self) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__init__.py index fc6a3a2..7246a41 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__init__.py @@ -1 +1,7 @@ -from .chip import MDChip, MDChipText # NOQA F401 +from .chip import ( + MDChip, + MDChipText, + MDChipLeadingIcon, + MDChipTrailingIcon, + MDChipLeadingAvatar, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/__init__.cpython-311.pyc index 93c52e1..9c8f35f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/chip.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/chip.cpython-311.pyc index 81f82ed..d9d49ae 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/chip.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/__pycache__/chip.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.kv index e008dd2..17d4ce9 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.kv @@ -1,29 +1,91 @@ + + icon_color: + ( \ + { \ + "filter": app.theme_cls.onSurfaceVariantColor, \ + "suggestion": app.theme_cls.onSurfaceVariantColor, \ + "input": app.theme_cls.onSurfaceVariantColor, \ + "assist": app.theme_cls.primaryColor, \ + }[self._type] \ + if self.theme_icon_color == "Primary" else self.icon_color \ + ) \ + if not root.disabled else self.disabled_color + disabled_color: + { \ + "filter": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_icon], \ + "suggestion": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_icon], \ + "input": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_icon], \ + "assist": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_icon], \ + }[self._type] \ + if not self.icon_color_disabled else self.icon_color_disabled + + + + font_style: "Label" + role: "large" + text_color: + ( \ + { \ + "filter": app.theme_cls.onSurfaceVariantColor, \ + "suggestion": app.theme_cls.onSurfaceVariantColor, \ + "input": app.theme_cls.onSurfaceVariantColor, \ + "assist": app.theme_cls.onSurfaceColor, \ + }[self._type] \ + if root.theme_text_color == "Primary" else root.text_color \ + ) \ + if not root.disabled else self.disabled_color + disabled_color: + { \ + "filter": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_text], \ + "suggestion": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_text], \ + "input": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_text], \ + "assist": app.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_text], \ + }[self._type] \ + if not self.text_color_disabled else self.text_color_disabled + + size_hint_y: None height: "32dp" adaptive_width: True radius: - 16 \ + dp(16) \ if self.radius == [0, 0, 0, 0] else \ - (max(self.radius) if max(self.radius) < self.height / 2 else 16) - md_bg_color: - ( \ - ( \ - app.theme_cls.bg_darkest \ - if app.theme_cls.theme_style == "Light" else \ - app.theme_cls.bg_light \ - ) \ - if not self._origin_md_bg_color else \ - self._origin_md_bg_color - ) \ - if not self.disabled else app.theme_cls.disabled_primary_color + (max(self.radius) if max(self.radius) < self.height / 2 else dp(16)) line_color: - app.theme_cls.disabled_hint_text_color \ - if self.disabled else ( \ - self._origin_line_color \ - if self._origin_line_color else \ - self.line_color \ + ( \ + ( \ + self.theme_cls.outlineColor \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + \ + [self.chip_opacity_value_disabled_container] \ + ) \ + if self.type != "filter" else \ + self.theme_cls.transparentColor \ + ) \ + if self.theme_line_color == "Primary" else \ + self._line_color if not self.disabled else \ + ( \ + self.line_color_disabled \ + if self.line_color_disabled else \ + self._line_color \ ) + md_bg_color: + { \ + "filter": self.theme_cls.surfaceContainerLowColor, \ + "suggestion": self.theme_cls.surfaceContainerLowColor, \ + "input": self.theme_cls.surfaceContainerLowColor, \ + "assist": self.theme_cls.surfaceContainerLowColor, \ + }[self.type] \ + if self.theme_bg_color == "Primary" else self.md_bg_color LeadingIconContainer: id: leading_icon_container diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.py index c9f3593..71d33d3 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/chip/chip.py @@ -4,7 +4,7 @@ Components/Chip .. seealso:: - `Material Design spec, Chips `_ + `Material Design 3 spec, Chips `_ .. rubric:: Chips can show multiple interactive elements together in the same area, such as a list of selectable movie times, or a series of email @@ -17,6 +17,25 @@ Components/Chip Usage ----- +.. code-block:: kv + + MDChip: + + MDChipLeadingAvatar: # MDChipLeadingIcon + + MDChipText: + + MDChipTrailingIcon: + +Anatomy +======= + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip-anatomy.png + :align: center + +Example +------- + .. tabs:: .. tab:: Declarative KV style @@ -75,34 +94,6 @@ Usage .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/chip.png :align: center -Anatomy -------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/anatomy-chip.png - :align: center - -1. Container -2. Label text -3. Leading icon or image (optional) -4. Trailing remove icon (optional, input & filter chips only) - -Container ---------- - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/radius-chip.png - :align: center - -All chips are slightly rounded with an 8dp corner. - -Shadows and elevation ---------------------- - -Chip containers can be elevated if the placement requires protection, such as -on top of an image. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadows-elevation-chip.png - :align: center - The following types of chips are available: ------------------------------------------- @@ -115,6 +106,7 @@ The following types of chips are available: - Suggestion_ .. Assist: + Assist ------ @@ -225,6 +217,7 @@ Example of assist :align: center .. Filter: + Filter ------ @@ -248,18 +241,22 @@ Example of filtering from kivymd.app import MDApp from kivymd.uix.chip import MDChip, MDChipText - from kivymd.uix.list import OneLineIconListItem + from kivymd.uix.list import MDListItem from kivymd.icon_definitions import md_icons from kivymd.uix.screen import MDScreen - from kivymd.utils import asynckivy + + import asynckivy Builder.load_string( ''' - IconLeftWidget: + MDListItemLeadingIcon: icon: root.icon + MDListItemHeadlineText: + text: root.text + @@ -270,11 +267,15 @@ Example of filtering MDTextField: id: search_field - hint_text: "Search icon" - mode: "rectangle" - icon_left: "magnify" + mode: "outlined" on_text: root.set_list_md_icons(self.text, True) + MDTextFieldLeadingIcon: + icon: "magnify" + + MDTextFieldHintText: + text: "Search icon" + MDBoxLayout: id: chip_box spacing: "12dp" @@ -294,18 +295,19 @@ Example of filtering orientation: "vertical" ''' ) - - - class CustomOneLineIconListItem(OneLineIconListItem): + + + class CustomOneLineIconListItem(MDListItem): icon = StringProperty() - - + text = StringProperty() + + class PreviewIconsScreen(MDScreen): filter = ListProperty() # list of tags for filtering icons - + def set_filter_chips(self): '''Asynchronously creates and adds chips to the container.''' - + async def set_filter_chips(): for tag in ["Outline", "Off", "On"]: await asynckivy.sleep(0) @@ -318,7 +320,7 @@ Example of filtering ) chip.bind(active=lambda x, y, z=tag: self.set_filter(y, z)) self.ids.chip_box.add_widget(chip) - + asynckivy.start(set_filter_chips()) def set_filter(self, active: bool, tag: str) -> None: @@ -358,7 +360,6 @@ Example of filtering def build(self) -> PreviewIconsScreen: self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "LightGreen" return self.screen def on_start(self) -> None: @@ -380,7 +381,8 @@ Tap a chip to select it. Multiple chips can be selected or unselected: from kivymd.app import MDApp from kivymd.uix.chip import MDChip, MDChipText from kivymd.uix.screen import MDScreen - from kivymd.utils import asynckivy + + import asynckivy Builder.load_string( ''' @@ -402,10 +404,12 @@ Tap a chip to select it. Multiple chips can be selected or unselected: MDWidget: - MDFlatButton: - text: "Uncheck chips" + MDButton: pos: "20dp", "20dp" on_release: root.unchecks_chips() + + MDButtonText: + text: "Uncheck chips" ''' ) @@ -442,7 +446,6 @@ Tap a chip to select it. Multiple chips can be selected or unselected: def build(self) -> ChipScreen: self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "LightGreen" return self.screen def on_start(self) -> None: @@ -465,7 +468,8 @@ menus: from kivymd.app import MDApp from kivymd.uix.chip import MDChip, MDChipText from kivymd.uix.screen import MDScreen - from kivymd.utils import asynckivy + + import asynckivy Builder.load_string( ''' @@ -485,11 +489,6 @@ menus: spacing: "12dp" adaptive_height: True - MDFillRoundFlatButton: - text: "Add to cart" - md_bg_color: "green" - size_hint_x: 1 - MDWidget: ''' ) @@ -542,6 +541,7 @@ menus: :align: center .. Input: + Input ----- @@ -595,6 +595,7 @@ Example of input :align: center .. Suggestion: + Suggestion ---------- @@ -641,7 +642,7 @@ Example of suggestion API break ========= -1.1.1 version +1.2.0 version ------------- .. code-block:: python @@ -670,7 +671,7 @@ API break Test().run() -1.2.0 version +2.0.0 version ------------- .. code-block:: python @@ -724,7 +725,6 @@ from kivy.properties import ( BooleanProperty, ColorProperty, OptionProperty, - StringProperty, VariableListProperty, ) from kivy.uix.behaviors import ButtonBehavior @@ -738,6 +738,7 @@ from kivymd.uix.behaviors import ( ScaleBehavior, TouchBehavior, ) +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.label import MDIcon, MDLabel @@ -750,6 +751,29 @@ with open( class BaseChipIcon( CircularRippleBehavior, ScaleBehavior, ButtonBehavior, MDIcon ): + icon_color = ColorProperty(None) + """ + Button icon color in (r, g, b, a) or string format. + + :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + icon_color_disabled = ColorProperty(None) + """ + The icon color in (r, g, b, a) or string format of the chip when + the chip is disabled. + + .. versionadded:: 2.0.0 + + :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + _type = OptionProperty( + "suggestion", options=["assist", "filter", "input", "suggestion"] + ) + def __init__(self, **kwargs): super().__init__(**kwargs) self.ripple_scale = 1.5 @@ -760,7 +784,8 @@ class BaseChipIcon( # icon size according to the standards of material design version 3. if ( self.font_name == "Icons" - and self.theme_cls.font_styles["Icon"][1] == self.font_size + and self.theme_cls.font_styles["Icon"]["large"]["font-size"] + == self.font_size ): self.font_size = ( "18sp" @@ -792,10 +817,10 @@ class MDChipLeadingAvatar(BaseChipIcon): Implements the leading avatar for the chip. For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~kivymd.uix.behaviors.ScaleBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.label.MDIcon` + :class:`~kivymd.uix.label.label.MDIcon` classes documentation. """ @@ -805,10 +830,10 @@ class MDChipLeadingIcon(BaseChipIcon): Implements the leading icon for the chip. For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~kivymd.uix.behaviors.ScaleBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.label.MDIcon` + :class:`~kivymd.uix.label.label.MDIcon` classes documentation. """ @@ -818,10 +843,10 @@ class MDChipTrailingIcon(BaseChipIcon): Implements the trailing icon for the chip. For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~kivymd.uix.behaviors.ScaleBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.label.MDIcon` + :class:`~kivymd.uix.label.label.MDIcon` classes documentation. """ @@ -831,9 +856,24 @@ class MDChipText(MDLabel): Implements the label for the chip. For more information, see in the - :class:`~kivymd.uix.label.MDLabel` classes documentation. + :class:`~kivymd.uix.label.label.MDLabel` classes documentation. """ + text_color_disabled = ColorProperty(None) + """ + The text color in (r, g, b, a) or string format of the chip when + the chip is disabled. + + .. versionadded:: 2.0.0 + + :attr:`text_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + _type = OptionProperty( + "suggestion", options=["assist", "filter", "input", "suggestion"] + ) + class MDChip( MDBoxLayout, @@ -841,16 +881,17 @@ class MDChip( ButtonBehavior, CommonElevationBehavior, TouchBehavior, + StateLayerBehavior, ): """ Chip class. For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` and - :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.behaviors.CommonElevationBehavior` and - :class:`~kivymd.uix.behaviors.TouchBehavior` + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior` classes documentation. """ @@ -862,23 +903,13 @@ class MDChip( and defaults to `[dp(8), dp(8), dp(8), dp(8)]`. """ - text = StringProperty(deprecated=True) - """ - Chip text. - - .. deprecated:: 1.2.0 - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - type = OptionProperty( "suggestion", options=["assist", "filter", "input", "suggestion"] ) """ Type of chip. - .. versionadded:: 1.2.0 + .. versionadded:: 2.0.0 Available options are: `'assist'`, `'filter'`, `'input'`, `'suggestion'`. @@ -886,74 +917,6 @@ class MDChip( and defaults to `'suggestion'`. """ - icon_left = StringProperty(deprecated=True) - """ - Chip left icon. - - .. versionadded:: 1.0.0 - - .. deprecated:: 1.2.0 - - :attr:`icon_left` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_right = StringProperty(deprecated=True) - """ - Chip right icon. - - .. versionadded:: 1.0.0 - - .. deprecated:: 1.2.0 - - :attr:`icon_right` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text_color = ColorProperty(None, deprecated=True) - """ - Chip's text color in (r, g, b, a) or string format. - - .. deprecated:: 1.2.0 - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_right_color = ColorProperty(None, deprecated=True) - """ - Chip's right icon color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - .. deprecated:: 1.2.0 - - :attr:`icon_right_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_left_color = ColorProperty(None, deprecated=True) - """ - Chip's left icon color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - .. deprecated:: 1.2.0 - - :attr:`icon_left_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_check_color = ColorProperty(None) - """ - Chip's check icon color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - :attr:`icon_check_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - active = BooleanProperty(False) """ Whether the check is marked or not. @@ -969,12 +932,23 @@ class MDChip( The background color of the chip in the marked state in (r, g, b, a) or string format. - .. versionadded:: 1.2.0 + .. versionadded:: 2.0.0 :attr:`selected_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ + line_color_disabled = ColorProperty(None) + """ + The color of the outline in the disabled state + + .. versionadded:: 2.0.0 + + :attr:`line_color_disabled` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + _line_color = ColorProperty(None) _current_md_bg_color = ColorProperty(None) # A flag that disallow ripple animation of the chip # at the time of clicking the chip icons. @@ -986,11 +960,19 @@ class MDChip( super().__init__(*args, **kwargs) def on_long_touch(self, *args) -> None: + """Fired when the widget is pressed for a long time.""" + if self.type == "filter": self.active = not self.active + def on_line_color(self, instance, value) -> None: + """Fired when the values of :attr:`line_color` change.""" + + if not self.disabled: + self._line_color = value + def on_type(self, instance, value: str) -> None: - """Called when the values of :attr:`type` change.""" + """Fired when the values of :attr:`type` change.""" def adjust_padding(*args): """ @@ -1069,7 +1051,14 @@ class MDChip( self.set_chip_bg_color( self.selected_color if self.selected_color - else self.theme_cls.primary_color + else { + "filter": self.theme_cls.surfaceContainerLowColor, + "suggestion": self.theme_cls.surfaceContainerLowColor, + "input": self.theme_cls.surfaceContainerLowColor, + "assist": self.theme_cls.surfaceContainerLowColor, + }[self.type] + if self.theme_bg_color == "Primary" + else self.md_bg_color ) else: if ( @@ -1125,11 +1114,26 @@ class MDChip( Animation(md_bg_color=color, d=0.2).start(self) self._anim_complete = not self._anim_complete - def on_press(self, *args): + def on_press(self, *args) -> None: + """Fired when the button is pressed.""" + if self.active: self.active = False + self._on_press(args) + + def on_release(self, *args) -> None: + """ + Fired when the button is released + (i.e. the touch/click that pressed the button goes away). + """ + + self._on_release(args) + def add_widget(self, widget, *args, **kwargs): + def set_type(*args): + widget._type = self.type + def add_icon_leading_trailing(container): if len(container.children): type_icon = ( @@ -1213,6 +1217,7 @@ class MDChip( container.add_widget(widget) if isinstance(widget, MDChipText): + Clock.schedule_once(set_type) widget.adaptive_size = True widget.pos_hint = {"center_y": 0.5} if self.type == "suggestion": @@ -1221,12 +1226,14 @@ class MDChip( lambda x: self.ids.label_container.add_widget(widget) ) elif isinstance(widget, (MDChipLeadingIcon, MDChipLeadingAvatar)): + Clock.schedule_once(set_type) Clock.schedule_once( lambda x: add_icon_leading_trailing( self.ids.leading_icon_container ) ) elif isinstance(widget, MDChipTrailingIcon): + Clock.schedule_once(set_type) Clock.schedule_once( lambda x: add_icon_leading_trailing( self.ids.trailing_icon_container @@ -1236,6 +1243,10 @@ class MDChip( widget, (LabelTextContainer, LeadingIconContainer, TrailingIconContainer), ): + if isinstance( + widget, (LeadingIconContainer, TrailingIconContainer) + ): + Clock.schedule_once(set_type) return super().add_widget(widget) def _set_allow_chip_ripple( diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/circularlayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/circularlayout.py index cca5d21..f7b343b 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/circularlayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/circularlayout.py @@ -16,7 +16,7 @@ MDCircularLayout from kivymd.app import MDApp - kv = ''' + KV = ''' MDScreen: MDCircularLayout: @@ -26,20 +26,18 @@ MDCircularLayout ''' - class Main(MDApp): + class Example(MDApp): def build(self): - return Builder.load_string(kv) + return Builder.load_string(KV) def on_start(self): for x in range(1, 49): - self.root.ids.container.add_widget( - Label(text=f"{x}", color=[0, 0, 0, 1]) - ) + self.root.ids.container.add_widget(Label(text=f"{x}") - Main().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-layout-dark.png :align: center """ @@ -48,11 +46,10 @@ __all__ = ("MDCircularLayout",) from math import atan2, cos, degrees, radians, sin from kivy.properties import BooleanProperty, NumericProperty - -from kivymd.uix.floatlayout import MDFloatLayout +from kivy.uix.floatlayout import FloatLayout -class MDCircularLayout(MDFloatLayout): +class MDCircularLayout(FloatLayout): degree_spacing = NumericProperty(30) """ The space between children in degree. @@ -63,7 +60,8 @@ class MDCircularLayout(MDFloatLayout): circular_radius = NumericProperty(None, allownone=True) """ - Radius of circle. Radius will be the greatest value in the layout if `circular_radius` if not specified. + Radius of circle. Radius will be the greatest value in the layout + if `circular_radius` if not specified. :attr:`circular_radius` is an :class:`~kivy.properties.NumericProperty` and defaults to `None`. @@ -79,7 +77,8 @@ class MDCircularLayout(MDFloatLayout): max_degree = NumericProperty(360) """ - Maximum range in degree allowed for each row of widgets before jumping to the next row. + Maximum range in degree allowed for each row of widgets before jumping + to the next row. :attr:`max_degree` is an :class:`~kivy.properties.NumericProperty` and defaults to `360`. @@ -185,7 +184,7 @@ if __name__ == "__main__": from kivymd.app import MDApp - kv = """ + KV = """ MDScreen: MDCircularLayout: @@ -194,14 +193,12 @@ MDScreen: row_spacing: min(self.size) * 0.1 """ - class Main(MDApp): + class Example(MDApp): def build(self): - return Builder.load_string(kv) + return Builder.load_string(KV) def on_start(self): for x in range(1, 49): - self.root.ids.container.add_widget( - Label(text=f"{x}", color=[0, 0, 0, 1]) - ) + self.root.ids.container.add_widget(Label(text=f"{x}")) - Main().run() + Example().run() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/__init__.cpython-311.pyc index fe18f04..d700e35 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/windowcontroller.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/windowcontroller.cpython-311.pyc index fbdfc73..c77686f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/windowcontroller.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/controllers/__pycache__/windowcontroller.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__init__.py deleted file mode 100644 index fc4115d..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .datatables import MDDataTable # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6701f2f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__pycache__/datatables.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__pycache__/datatables.cpython-311.pyc deleted file mode 100644 index fc5c193..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/__pycache__/datatables.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/datatables.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/datatables.kv deleted file mode 100644 index b5d81a7..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/datatables.kv +++ /dev/null @@ -1,241 +0,0 @@ -#:import DEVICE_TYPE kivymd.material_resources.DEVICE_TYPE - - - - orientation: "vertical" - md_bg_color: - ( \ - ( \ - root.theme_cls.bg_darkest \ - if root.theme_cls.theme_style == "Light" else \ - root.theme_cls.bg_light \ - ) \ - if not root.background_color_selected_cell \ - else root.background_color_selected_cell \ - ) \ - if self.selected \ - else \ - ( \ - root.theme_cls.bg_normal \ - if not root.background_color_cell \ - else root.background_color_cell \ - ) - on_press: if DEVICE_TYPE != "desktop": root.table.on_mouse_select(self) - on_enter: if DEVICE_TYPE == "desktop": root.table.on_mouse_select(self) - - MDBoxLayout: - id: box - padding: "8dp", "8dp", 0, "8dp" - spacing: "16dp" - - MDCheckbox: - id: check - size_hint: None, None - size: 0, 0 - opacity: 0 - - MDBoxLayout: - id: inner_box - - MDIcon: - id: icon - size_hint: None, None - pos_hint: {"center_y": 0.5} - size: ("24dp", "24dp") if root.icon else (0, 0) - icon: root.icon if root.icon else "" - theme_text_color: "Custom" - text_color: - root.icon_color if root.icon_color else \ - root.theme_cls.primary_color - - MDLabel: - id: label - text: " " + root.text - markup: True - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDSeparator: - - - - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - spacing: "4dp" - tooltip_text: root.tooltip if root.tooltip else root.text - - BoxLayout: - id: box - size_hint_y: None - height: lbl.height - - MDLabel: - id: lbl - text: " " + root.text - adaptive_height: True - bold: True - markup: True - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDSeparator: - id: separator - - - - id: sort_btn - icon: "arrow-up" - pos_hint: {"center_y": 0.5} - size: [dp(24), dp(0)] - theme_text_color: "Custom" - text_color: self.theme_cls.secondary_text_color - opacity: 0 - - - - bar_width: 0 - do_scroll: False - size_hint: 1, None - height: header.height - - MDGridLayout: - id: header - rows: 1 - cols_minimum: root.cols_minimum - adaptive_size: True - padding: 0, "8dp", 0, 0 - md_bg_color: - root.theme_cls.bg_light \ - if not root.background_color_header \ - else root.background_color_header - - MDBoxLayout: - orientation: "vertical" - - MDBoxLayout: - id: box - padding: "8dp", "8dp", "4dp", 0 - spacing: "16dp" - - MDCheckbox: - id: check - size_hint: None, None - size: 0, 0 - opacity: 0 - on_release: root.table_data.select_all(self.state) - - CellHeader: - id: first_cell - - MDSeparator: - - - - data: root.recycle_data - data_first_cells: root.data_first_cells - key_viewclass: "viewclass" - - TableRecycleGridLayout: - id: row_controller - key_selection: "selectable" - cols: root.total_col_headings - cols_minimum: root.cols_minimum - default_size: None, dp(52) - default_size_hint: 1, None - size_hint: None, None - height: self.minimum_height - width: self.minimum_width - multiselect: True - touch_multiselect: True - - - - adaptive_height: True - spacing: "8dp" - - MDLabel: - text: "Rows per page" - shorten: True - halign: "right" - font_style: "Caption" - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - - MDDropDownItem: - id: drop_item - pos_hint: {'center_y': .5} - font_size: "14sp" - on_release: root.table_data.open_pagination_menu() - text: - "{}".format( \ - root.table_data.rows_num \ - if root.table_data.rows_num < len(root.table_data.row_data) else \ - len(root.table_data.row_data) \ - ) - - Widget: - size_hint_x: None - width: "32dp" if DEVICE_TYPE != "mobile" else "8dp" - - MDLabel: - id: label_rows_per_page - adaptive_size: True - -text_size: None, None - pos_hint: {"center_y": .5} - font_style: "Caption" - color: - (1, 1, 1, 1) \ - if root.theme_cls.theme_style == "Dark" else \ - (0, 0, 0, 1) - text: - "1-{} of {}".format( \ - root.table_data.rows_num \ - if root.table_data.rows_num > len(root.table_data.row_data) else \ - len(root.table_data.row_data), len(root.table_data.row_data) \ - ) - - MDIconButton: - id: button_back - icon: "chevron-left" - user_font_size: "20sp" if DEVICE_TYPE != "mobile" else "16dp" - ripple_scale: .5 if DEVICE_TYPE == "mobile" else 1 - pos_hint: {'center_y': .5} - disabled: True - md_bg_color_disabled: 0, 0, 0, 0 - on_release: root.table_data.set_next_row_data_parts("back") - - MDIconButton: - id: button_forward - icon: "chevron-right" - user_font_size: "20sp" if DEVICE_TYPE != "mobile" else "16dp" - ripple_scale: .5 if DEVICE_TYPE == "mobile" else 1 - pos_hint: {'center_y': .5} - disabled: True - md_bg_color_disabled: 0, 0, 0, 0 - on_release: root.table_data.set_next_row_data_parts("forward") - - - - - - - - TableContainer: - id: container - orientation: "vertical" - elevation: root.elevation - shadow_radius: root.shadow_radius - shadow_softness: root.shadow_softness - shadow_offset: root.shadow_offset - shadow_color: root.shadow_color - shadow_color: root.shadow_color - shadow_softness_size: root.shadow_softness_size - padding: "24dp", "24dp", "8dp", "8dp" - md_bg_color: app.theme_cls.bg_normal diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/datatables.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/datatables.py deleted file mode 100644 index 44748a5..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/datatables/datatables.py +++ /dev/null @@ -1,2113 +0,0 @@ -""" -Components/DataTables -===================== - -.. seealso:: - - `Material Design spec, DataTables `_ - -.. rubric:: Data tables display sets of data across rows and columns. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-previous.png - :align: center - -.. note:: `MDDataTable` allows developers to sort the data provided by column. - This happens thanks to the use of an external function that you can bind - while you're defining the table columns. Be aware that the sorting function - must return a 2 value list in the format of: - `[Index, Sorted_Row_Data]` - - This is because the index list is needed to allow MDDataTable to keep track - of the selected rows. and, after the data is sorted, update the row - checkboxes. - -""" - -# Special thanks for the info - -# https://stackoverflow.com/questions/50219281/python-how-to-add-vertical-scroll-in-recycleview - -__all__ = ("MDDataTable",) - -import os -from collections import defaultdict -from typing import Union - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - BoundedNumericProperty, - ColorProperty, - DictProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, - VariableListProperty, -) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ButtonBehavior, FocusBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.recyclegridlayout import RecycleGridLayout -from kivy.uix.recycleview import RecycleView -from kivy.uix.recycleview.layout import LayoutSelectionBehavior -from kivy.uix.recycleview.views import RecycleDataViewBehavior -from kivy.uix.scrollview import ScrollView - -from kivymd import uix_path -from kivymd.effects.stiffscroll import StiffScrollEffect -from kivymd.material_resources import ( - DATA_TABLE_ELEVATION, - DATA_TABLE_OFFSET, - DATA_TABLE_SOFTNESS, -) -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import HoverBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDIconButton -from kivymd.uix.menu import MDDropdownMenu -from kivymd.uix.selectioncontrol import MDCheckbox -from kivymd.uix.tooltip import MDTooltip - -with open( - os.path.join(uix_path, "datatables", "datatables.kv"), encoding="utf-8" -) as kv_file: - Builder.load_string(kv_file.read()) - - -class TableRecycleGridLayout( - FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout -): - selected_row = NumericProperty(0) - table_data = ObjectProperty(None) - - def get_nodes(self): - nodes = self.get_selectable_nodes() - if self.nodes_order_reversed: - nodes = nodes[::-1] - if not nodes: - return None, None - - selected = self.selected_nodes - if not selected: # nothing selected, select the first - self.selected_row = 0 - self.select_row(nodes) - return None, None - - if len(nodes) == 1: # the only selectable node is selected already - return None, None - - index = selected[-1] - if index > len(nodes): - last = len(nodes) - else: - last = nodes.index(index) - self.clear_selection() - return last, nodes - - def select_next(self, instance): - """Select next row.""" - - self.table_data = instance - last, nodes = self.get_nodes() - if not nodes: - return - - if last == len(nodes) - 1: - self.selected_row = nodes[0] - else: - self.selected_row = nodes[last + 1] - - self.selected_row += self.table_data.total_col_headings - self.select_row(nodes) - - def select_current(self, instance): - """Select current row.""" - - self.table_data = instance - last, nodes = self.get_nodes() - if not nodes: - return - - self.select_row(nodes) - - def select_row(self, nodes): - col = self.table_data.recycle_data[self.selected_row]["range"] - for x in range(col[0], col[1] + 1): - self.select_node(nodes[x]) - - -class CellHeader(MDTooltip, BoxLayout): - """ - Implements the label text in the column header panel from - :attr:`~MDDataTable.column_data` data. - """ - - text = StringProperty() - """ - Column text. - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tooltip = StringProperty() - """ - Tooltip containing descriptive text for the column. - If the tooltip is not provided, column `text` shall be used instead. - - :attr:`tooltip` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - # TODO: Added example. - sort_action = ObjectProperty() - """ - Custom function for sorting. - - :attr:`sort_action` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - table_data = ObjectProperty() - """ - :class:`~TableData` class. - - :attr:`table_data` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - is_sorted = BooleanProperty(False) - sorted_order = StringProperty() - - def __init__(self, *args, **kwargs): - super().__init__(**kwargs) - if self.sort_action: - box = self.ids.box - ib = SortButton() - ib.bind(on_release=self._sort_release) - - if self.is_sorted: - ib.icon = ( - "arrow-down" if self.sorted_order == "ASC" else "arrow-up" - ) - ib.size = [dp(24), dp(24)] - ib.opacity = 1 - else: - self.bind(on_enter=self.set_sort_btn) - self.bind(on_leave=self.set_sort_btn) - - box.add_widget(ib, index=1) - - def restore_checks(self, indices: dict) -> None: - curr_checks = self.table_data.current_selection_check - rows_num = self.table_data.rows_num - columns = self.table_data.total_col_headings - - new_checks = defaultdict(list) - for i, x in enumerate(curr_checks): - for j, y in enumerate(curr_checks[x]): - new_page = (indices[y // columns + x * rows_num]) // rows_num - new_indice = ( - (indices[y // columns + x * rows_num]) % rows_num - ) * columns - new_checks[new_page].append(new_indice) - self.table_data.current_selection_check = dict(new_checks) - - def set_sort_btn(self, instance_cell_header) -> None: - btn = instance_cell_header.ids.box.children[-1] - if btn.opacity: - btn.size = [dp(24), dp(0)] - btn.opacity = 0 - else: - btn.size = [dp(24), dp(24)] - btn.opacity = 1 - - def _sort_release(self, inst): - inst.icon = "arrow-down" if inst.icon == "arrow-up" else "arrow-up" - - if not self.parent.parent._col_with_sort: - c = self.parent.children - col_with_sort = [ - each - for each in c - if each.ids.get("box", None) and len(each.ids.box.children) == 2 - ] - self.parent.parent._col_with_sort = col_with_sort - else: - col_with_sort = self.parent.parent._col_with_sort - - for each in col_with_sort: - if each == self: - self.unbind(on_enter=self.set_sort_btn) - self.unbind(on_leave=self.set_sort_btn) - else: - btn = each.ids.box.children[-1] - btn.size = [dp(24), dp(0)] - btn.opacity = 0 - each.bind(on_enter=each.set_sort_btn) - each.bind(on_leave=each.set_sort_btn) - - if self.sort_action: - if not self.table_data: - th = self.parent.parent - self.table_data = th.table_data - - indices, sorted_data = self.sort_action(self.table_data.row_data) - - if not sorted_data: - return - - if inst.icon == "arrow-down": - sorted_data = sorted_data[::-1] - indices = indices[::-1] - - self.table_data.row_data = sorted_data - self.table_data.on_rows_num(self, self.table_data.rows_num) - self.restore_checks(dict(zip(indices, range(len(indices))))) - self.table_data.set_next_row_data_parts("reset") - self.table_data.cell_row_obj_dict = {} - self.table_data.table_header.ids.check.state = "normal" - - -class TableHeader(ThemableBehavior, ScrollView): - """ - Implements a panel for column heading labels - - :attr:`~MDDataTable.column_data`. - """ - - table_data = ObjectProperty() - """ - Class :class:`~TableData`. - - :attr:`table_data` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - column_data = ListProperty() - """ - See :attr:`~MDDataTable.sorted_on` - - :attr:`column_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - sorted_on = StringProperty() - """ - See :attr:`~MDDataTable.sorted_on`. - - :attr:`sorted_on` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - cols_minimum = DictProperty() - """ - See :attr:`~kivy.uix.gridlayout.GridLayout.cols_minimum`. - - :attr:`cols_minimum` is an :class:`~kivy.properties.DictProperty` - and defaults to `{}`. - """ - - sorted_order = StringProperty() - """ - See :attr:`~MDDataTable.sorted_order`. - - :attr:`sorted_order` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - background_color_header = ColorProperty(None) - """ - See :attr:`~MDDataTable.background_color_header`. - - .. versionadded:: 1.0.0 - - :attr:`background_color_header` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - _col_with_sort = [] # store cols which contain sort functions - _col_headings = ListProperty() # column names list - - def __init__(self, **kwargs): - super().__init__(**kwargs) - # Create cells. - for i, col_heading in enumerate(self.column_data): - self.cols_minimum[i] = col_heading[1] * 5 - self._col_headings.append(col_heading[0]) - if i: - self.ids.header.add_widget( - ( - CellHeader( - text=col_heading[0], - sort_action=col_heading[2], - tooltip=col_heading[3], - width=self.cols_minimum[i], - table_data=self.table_data, - is_sorted=(col_heading[0] == self.sorted_on), - sorted_order=self.sorted_order, - ) - if len(col_heading) == 4 - else CellHeader( - text=col_heading[0], - sort_action=col_heading[2], - width=self.cols_minimum[i], - table_data=self.table_data, - ) - if len(col_heading) == 3 - else CellHeader( - text=col_heading[0], - width=self.cols_minimum[i], - table_data=self.table_data, - ) - ) - ) - else: - # Sets the text in the first cell. - self.ids.first_cell.text = col_heading[0] - self.ids.first_cell.tooltip = ( - col_heading[3] if len(col_heading) == 4 else "" - ) - self.ids.first_cell.ids.separator.height = 0 - self.ids.first_cell.width = self.cols_minimum[i] - - def on_table_data(self, instance_table_header, instance_table_data) -> None: - """Sets the checkbox in the first cell.""" - - if self.table_data.check: - self.ids.check.size = (dp(32), dp(32)) - self.ids.check.opacity = 1 - else: - self.ids.box.padding[0] = 0 - self.ids.box.spacing = 0 - - -class TableData(RecycleView): - """Implements a list of table data.""" - - recycle_data = ListProperty() - """ - See :attr:`~kivy.uix.recycleview.RecycleView.data`. - - :attr:`recycle_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - data_first_cells = ListProperty() - """ - List of first row cells. - - :attr:`data_first_cells` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - row_data = ListProperty() - """ - See :attr:`~MDDataTable.row_data`. - - :attr:`row_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - total_col_headings = NumericProperty(0) # TableHeader._col_headings - """ - See :attr:`~TableHeader._col_headings`. - - :attr:`total_col_headings` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - cols_minimum = DictProperty() - """ - See :attr:`~TableHeader.cols_minimum`. - - :attr:`cols_minimum` is an :class:`~kivy.properties.DictProperty` - and defaults to `{}`. - """ - - table_header = ObjectProperty() - """ - :class:`~TableHeader` class. - - :attr:`table_header` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - pagination_menu = ObjectProperty() - """ - :class:`~kivymd.uix.menu.MDDropdownMenu` class. - - :attr:`pagination_menu` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - pagination = ObjectProperty() - """ - :class:`~TablePagination` class. - - :attr:`pagination` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - check = ObjectProperty() - """ - See :attr:`~MDDataTable.check`. - - :attr:`check` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - rows_num = NumericProperty() - """ - Number of rows displayed on the table page. - - :attr:`rows_num` is an :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - pagination_menu_open = BooleanProperty(False) - """ - Open or close the menu for selecting the number of rows displayed - on the table page. - - :attr:`pagination_menu_open` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - current_selection_check = DictProperty() - """ - List of indexes of marked checkboxes. - - :attr:`current_selection_check` is an :class:`~kivy.properties.DictProperty` - and defaults to `{}`. - """ - - cell_row_obj_dict = {} - - _parent = ObjectProperty() - _rows_number = NumericProperty(0) - _rows_num = NumericProperty() - _current_value = NumericProperty(1) - _to_value = NumericProperty() - _row_data_parts = ListProperty() - - def __init__(self, table_header, **kwargs): - super().__init__(**kwargs) - self.table_header = table_header - self.total_col_headings = len(table_header._col_headings) - self.cols_minimum = table_header.cols_minimum - self.set_row_data() - self.effect_cls = self._parent.effect_cls - Clock.schedule_once(self.set_default_first_row, 0) - - def get_select_row(self, index: int) -> None: - """Returns the current row with all elements.""" - - row = [] - for data in self.recycle_data: - if index in data["range"]: - row.append(data["text"]) - self._parent.dispatch("on_check_press", row) - self._get_row_checks() # update the dict - - def set_default_first_row(self, interval: Union[int, float]) -> None: - """Set default first row as selected.""" - - self.ids.row_controller.select_next(self) - - def set_row_data(self) -> None: - data = [] - low = 0 - high = self.total_col_headings - 1 - self.recycle_data = [] - self.data_first_cells = [] - - if self._row_data_parts: - # for row in self.row_data: - for row in self._row_data_parts[self._rows_number]: - for i in range(len(row)): - data.append([row[i], row[0], [low, high]]) - low += self.total_col_headings - high += self.total_col_headings - - for j, x in enumerate(data): - if x[0] == x[1]: - self.data_first_cells.append(x[2][0]) - self.recycle_data.append( - { - "text": str(x[0]), - "Index": str(j), - "range": x[2], - "selectable": True, - "viewclass": "CellRow", - "table": self, - "background_color_cell": self._parent.background_color_cell, - "background_color_selected_cell": self._parent.background_color_selected_cell, - } - ) - else: - r_data = { - "Index": str(j), - "range": x[2], - "selectable": True, - "viewclass": "CellRow", - "table": self, - "background_color_cell": self._parent.background_color_cell, - "background_color_selected_cell": self._parent.background_color_selected_cell, - } - - if ( - isinstance(x[0], tuple) or isinstance(x[0], list) - ) and len(x[0]) == 3: - r_data["icon"] = x[0][0] - r_data["icon_color"] = x[0][1] - r_data["text"] = str(x[0][2]) - self.recycle_data.append(r_data) - - elif ( - isinstance(x[0], tuple) or isinstance(x[0], list) - ) and len(x[0]) == 2: - r_data["icon"] = x[0][0] - r_data["text"] = str(x[0][1]) - - self.recycle_data.append(r_data) - - else: - r_data["text"] = str(x[0]) - self.recycle_data.append(r_data) - - if not self.table_header.column_data: - raise ValueError("Set value for column_data in class TableData") - self.data_first_cells.append(self.table_header.column_data[0][0]) - - def set_text_from_of(self, direction: str) -> None: - """Sets the text of the numbers of displayed pages in table.""" - - if self.pagination: - if direction == "reset": - self._current_value = 1 - self._to_value = len(self._row_data_parts[self._rows_number]) - elif direction == "forward": - if ( - len(self._row_data_parts[self._rows_number]) - < self._to_value - ): - self._current_value = self._current_value + self.rows_num - else: - self._current_value = self._current_value + len( - self._row_data_parts[self._rows_number] - ) - self._to_value = self._to_value + len( - self._row_data_parts[self._rows_number] - ) - if direction == "back": - self._current_value = self._current_value - len( - self._row_data_parts[self._rows_number] - ) - self._to_value = self._to_value - len( - self._row_data_parts[self._rows_number + 1] - ) - if direction == "increment": - self._current_value = 1 - self._to_value = self.rows_num + self._current_value - 1 - - self.pagination.ids.label_rows_per_page.text = ( - f"{self._current_value}-{self._to_value} " - f"of {len(self.row_data)}" - ) - - def select_all(self, state: str) -> None: - """Sets the checkboxes of all rows to the active/inactive position.""" - - for i in range(0, len(self.recycle_data), self.total_col_headings): - cell_row_obj = self.view_adapter.get_visible_view(i) - if cell_row_obj: - self.cell_row_obj_dict[i] = cell_row_obj - self.on_mouse_select(cell_row_obj) - cell_row_obj.ids.check.state = state - - if state == "down": - # select all checks on all pages - rows_num = self.rows_num - columns = self.total_col_headings - full_pages = len(self.row_data) // self.rows_num - left_over_rows = len(self.row_data) % self.rows_num - - new_checks = {} - for page in range(full_pages): - new_checks[page] = list(range(0, rows_num * columns, columns)) - - if left_over_rows: - new_checks[full_pages] = list( - range(0, left_over_rows * columns, columns) - ) - - self.current_selection_check = new_checks - return - - # resets all checks on all pages - self.current_selection_check = {} - - def check_all(self, state: str) -> bool: - """Checks if checkboxes of all rows are in the same state.""" - - tmp = [] - for i in range(0, len(self.recycle_data), self.total_col_headings): - if self.cell_row_obj_dict.get(i, None): - cell_row_obj = self.cell_row_obj_dict[i] - else: - cell_row_obj = self.view_adapter.get_visible_view(i) - if cell_row_obj: - self.cell_row_obj_dict[i] = cell_row_obj - if cell_row_obj: - tmp.append(cell_row_obj.ids.check.state == state) - return all(tmp) - - def close_pagination_menu(self, *args) -> None: - """Called when the pagination menu window is closed.""" - - self.pagination_menu_open = False - - def open_pagination_menu(self) -> None: - """Open pagination menu window.""" - - if self.pagination_menu.items: - self.pagination_menu_open = True - self.pagination_menu.open() - - def set_number_displayed_lines(self, text_item) -> None: - """ - Called when the user sets the number of pages displayed - in the table. - """ - - # self.rows_num = int(text_item) - self.rows_num = int(text_item) - self.set_next_row_data_parts("reset") - self.set_text_from_of("reset") - self.pagination_menu.caller.text = text_item - - def set_next_row_data_parts(self, direction: str) -> None: - """Called when switching the pages of the table.""" - - if direction == "reset": - self._rows_number = 0 - self.pagination.ids.button_back.disabled = True - self.pagination.ids.button_forward.disabled = False - elif direction == "forward": - self._rows_number += 1 - self.pagination.ids.button_back.disabled = False - elif direction == "back": - self._rows_number -= 1 - self.pagination.ids.button_forward.disabled = False - - self.set_row_data() - self.set_text_from_of(direction) - - if self._to_value == len(self.row_data): - self.pagination.ids.button_forward.disabled = True - if self._current_value == 1: - self.pagination.ids.button_back.disabled = True - - def on_mouse_select(self, instance_cell_row) -> None: - """Called on the ``on_enter`` event of the :class:`~CellRow` class.""" - - if not self.pagination_menu_open: - if self.ids.row_controller.selected_row != instance_cell_row.index: - self.ids.row_controller.selected_row = instance_cell_row.index - self.ids.row_controller.select_current(self) - - def on_rows_num(self, instance_table_date, value_rows_num: int) -> None: - if not self._to_value: - self._to_value = value_rows_num - - self._rows_number = 0 - self._row_data_parts = list( - self._split_list_into_equal_parts(self.row_data, value_rows_num) - ) - - def on_pagination( - self, instance_table_date, instance_table_pagination - ) -> None: - if self._to_value < len(self.row_data): - self.pagination.ids.button_forward.disabled = False - - def _split_list_into_equal_parts(self, lst, parts): - for i in range(0, len(lst), parts): - yield lst[i : i + parts] - - def _get_row_checks(self): - """Returns all rows that are checked.""" - - tmp = [] - for i in range(0, len(self.recycle_data), self.total_col_headings): - if self.cell_row_obj_dict.get(i, None): - cell_row_obj = self.cell_row_obj_dict[i] - else: - cell_row_obj = self.view_adapter.get_visible_view(i) - if cell_row_obj: - self.cell_row_obj_dict[i] = cell_row_obj - - if cell_row_obj and cell_row_obj.ids.check.state == "down": - idx = cell_row_obj.index - row = [] - for data in self.recycle_data: - if idx in data["range"]: - row.append(data["text"]) - - tmp.append(row) - return tmp - - # def on_pagination(self, instance_table, instance_pagination): - # if len(self._row_data_parts) <= self._to_value: - # instance_pagination.ids.button_forward.disabled = True - - -class TablePagination(MDBoxLayout): - """Pagination Container.""" - - table_data = ObjectProperty() - """ - :class:`~TableData` class. - - :attr:`table_data` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - -class MDDataTable(ThemableBehavior, AnchorLayout): - """ - Datatable class. - - For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.anchorlayout.AnchorLayout` classes documentation. - - :Events: - :attr:`on_row_press` - Called when a table row is clicked. - :attr:`on_check_press` - Called when the check box in the table row is checked. - - .. rubric:: Use events as follows - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivymd.uix.screen import MDScreen - - - class Example(MDApp): - def build(self): - self.data_tables = MDDataTable( - use_pagination=True, - check=True, - column_data=[ - ("No.", dp(30)), - ("Status", dp(30)), - ("Signal Name", dp(60), self.sort_on_signal), - ("Severity", dp(30)), - ("Stage", dp(30)), - ("Schedule", dp(30), self.sort_on_schedule), - ("Team Lead", dp(30), self.sort_on_team), - ], - row_data=[ - ( - "1", - ("alert", [255 / 256, 165 / 256, 0, 1], "No Signal"), - "Astrid: NE shared managed", - "Medium", - "Triaged", - "0:33", - "Chase Nguyen", - ), - ( - "2", - ("alert-circle", [1, 0, 0, 1], "Offline"), - "Cosmo: prod shared ares", - "Huge", - "Triaged", - "0:39", - "Brie Furman", - ), - ( - "3", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Phoenix: prod shared lyra-lists", - "Minor", - "Not Triaged", - "3:12", - "Jeremy lake", - ), - ( - "4", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Sirius: NW prod shared locations", - "Negligible", - "Triaged", - "13:18", - "Angelica Howards", - ), - ( - "5", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Sirius: prod independent account", - "Negligible", - "Triaged", - "22:06", - "Diane Okuma", - ), - ], - sorted_on="Schedule", - sorted_order="ASC", - elevation=2, - ) - self.data_tables.bind(on_row_press=self.on_row_press) - self.data_tables.bind(on_check_press=self.on_check_press) - screen = MDScreen() - screen.add_widget(self.data_tables) - return screen - - def on_row_press(self, instance_table, instance_row): - '''Called when a table row is clicked.''' - - print(instance_table, instance_row) - - def on_check_press(self, instance_table, current_row): - '''Called when the check box in the table row is checked.''' - - print(instance_table, current_row) - - # Sorting Methods: - # since the https://github.com/kivymd/KivyMD/pull/914 request, the - # sorting method requires you to sort out the indexes of each data value - # for the support of selections. - # - # The most common method to do this is with the use of the builtin function - # zip and enumerate, see the example below for more info. - # - # The result given by these funcitons must be a list in the format of - # [Indexes, Sorted_Row_Data] - - def sort_on_signal(self, data): - return zip(*sorted(enumerate(data), key=lambda l: l[1][2])) - - def sort_on_schedule(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: sum( - [ - int(l[1][-2].split(":")[0]) * 60, - int(l[1][-2].split(":")[1]), - ] - ), - ) - ) - - def sort_on_team(self, data): - return zip(*sorted(enumerate(data), key=lambda l: l[1][-1])) - - - Example().run() - """ - - column_data = ListProperty() - """ - Data for header columns. - - .. tabs:: - - .. tab:: Imperative python style - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivy.uix.anchorlayout import AnchorLayout - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - layout = AnchorLayout() - self.data_tables = MDDataTable( - size_hint=(0.7, 0.6), - use_pagination=True, - check=True, - # name column, width column, sorting function column(optional), custom tooltip - column_data=[ - ("No.", dp(30), None, "Custom tooltip"), - ("Status", dp(30)), - ("Signal Name", dp(60)), - ("Severity", dp(30)), - ("Stage", dp(30)), - ("Schedule", dp(30), lambda *args: print("Sorted using Schedule")), - ("Team Lead", dp(30)), - ], - ) - layout.add_widget(self.data_tables) - return layout - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.anchorlayout import MDAnchorLayout - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return MDAnchorLayout( - MDDataTable( - size_hint=(0.7, 0.6), - use_pagination=True, - check=True, - # name column, width column, sorting function column(optional) - column_data=[ - ("No.", dp(30)), - ("Status", dp(30)), - ("Signal Name", dp(60)), - ("Severity", dp(30)), - ("Stage", dp(30)), - ("Schedule", dp(30), - lambda *args: print("Sorted using Schedule")), - ("Team Lead", dp(30)), - ], - ) - ) - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-column-data.png - :align: center - - :attr:`column_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - - .. note:: The functions which will be called for sorting must accept a data - argument and return the sorted data. Incoming data format will be - similar to the provided row_data except that it'll be all list instead - of tuple like below. Any icon provided initially will also be there in - this data so handle accordingly. - - .. code-block:: python - - [ - [ - "1", - ["icon", "No Signal"], - "Astrid: NE shared managed", - "Medium", - "Triaged", - "0:33", - "Chase Nguyen", - ], - [ - "2", - "Offline", - "Cosmo: prod shared ares", - "Huge", - "Triaged", - "0:39", - "Brie Furman", - ], - [ - "3", - "Online", - "Phoenix: prod shared lyra-lists", - "Minor", - "Not Triaged", - "3:12", - "Jeremy lake", - ], - [ - "4", - "Online", - "Sirius: NW prod shared locations", - "Negligible", - "Triaged", - "13:18", - "Angelica Howards", - ], - [ - "5", - "Online", - "Sirius: prod independent account", - "Negligible", - "Triaged", - "22:06", - "Diane Okuma", - ], - ] - - You must sort inner lists in ascending order and return the sorted data - in the same format. - """ - - row_data = ListProperty() - """ - Data for rows. To add icon in addition to a row data, include a tuple with - This property stores the row data used to display each row in the DataTable - To show an icon inside a column in a row, use the folowing format in the - row's columns. - - Format: - - `("MDicon-name", [icon color in rgba], "Column Value")` - - Example: - - .. code-block:: python - [...] - row_data = [ - - # row 1 - [ - "value 1", - "value 2", - # the third value will have an icon inside the box - ["home", [128/255, 48/255, 76/255, 1], "Offie" ] - ], - - # row 2 - [ - "value 1", - "value 2", - # the third value will have an icon inside the box - ["git", [1, 0.1, 0.1, 1], "Git Repo" ] - ] - ] - - For a more complex example see below. - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - layout = AnchorLayout() - data_tables = MDDataTable( - size_hint=(0.9, 0.6), - column_data=[ - ("Column 1", dp(20)), - ("Column 2", dp(30)), - ("Column 3", dp(50), self.sort_on_col_3), - ("Column 4", dp(30)), - ("Column 5", dp(30)), - ("Column 6", dp(30)), - ("Column 7", dp(30), self.sort_on_col_2), - ], - row_data=[ - # The number of elements must match the length - # of the `column_data` list. - ( - "1", - ("alert", [255 / 256, 165 / 256, 0, 1], "No Signal"), - "Astrid: NE shared managed", - "Medium", - "Triaged", - "0:33", - "Chase Nguyen", - ), - ( - "2", - ("alert-circle", [1, 0, 0, 1], "Offline"), - "Cosmo: prod shared ares", - "Huge", - "Triaged", - "0:39", - "Brie Furman", - ), - ( - "3", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Phoenix: prod shared lyra-lists", - "Minor", - "Not Triaged", - "3:12", - "Jeremy lake", - ), - ( - "4", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Sirius: NW prod shared locations", - "Negligible", - "Triaged", - "13:18", - "Angelica Howards", - ), - ( - "5", - ( - "checkbox-marked-circle", - [39 / 256, 174 / 256, 96 / 256, 1], - "Online", - ), - "Sirius: prod independent account", - "Negligible", - "Triaged", - "22:06", - "Diane Okuma", - ), - ], - ) - layout.add_widget(data_tables) - return layout - - def sort_on_col_3(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: l[1][3] - ) - ) - - def sort_on_col_2(self, data): - return zip( - *sorted( - enumerate(data), - key=lambda l: l[1][-1] - ) - ) - - Example().run() - - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-row-data.png - :align: center - - :attr:`row_data` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - sorted_on = StringProperty() - """ - Column name upon which the data is already sorted. - - If the table data is showing an already sorted data then this can be used - to indicate upon which column the data is sorted. - - :attr:`sorted_on` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - sorted_order = OptionProperty("ASC", options=["ASC", "DSC"]) - """ - Order of already sorted data. Must be one of `'ASC'` for ascending or - `'DSC'` for descending order. - - :attr:`sorted_order` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'ASC'`. - """ - - check = BooleanProperty(False) - """ - Use or not use checkboxes for rows. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-check.png - :align: center - - :attr:`check` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - use_pagination = BooleanProperty(False) - """ - Use page pagination for table or not. - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - layout = AnchorLayout() - data_tables = MDDataTable( - size_hint=(0.9, 0.6), - use_pagination=True, - column_data=[ - ("No.", dp(30)), - ("Column 1", dp(30)), - ("Column 2", dp(30)), - ("Column 3", dp(30)), - ("Column 4", dp(30)), - ("Column 5", dp(30)), - ], - row_data=[ - (f"{i + 1}", "1", "2", "3", "4", "5") for i in range(50) - ], - ) - layout.add_widget(data_tables) - return layout - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination.png - :align: center - - :attr:`use_pagination` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - elevation = NumericProperty(DATA_TABLE_ELEVATION) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation` - attribute. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `4`. - """ - - shadow_radius = VariableListProperty([6], length=4) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_radius` - attribute. - - .. versionadded:: 1.2.0 - - :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[6]`. - """ - - shadow_softness = NumericProperty(DATA_TABLE_SOFTNESS) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness` - attribute. - - .. versionadded:: 1.2.0 - - :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` - and defaults to `12`. - """ - - shadow_softness_size = BoundedNumericProperty(2, min=2) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness_size` - attribute. - - .. versionadded:: 1.2.0 - - :attr:`shadow_softness_size` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `2`. - """ - - shadow_offset = ListProperty(DATA_TABLE_OFFSET) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset` - attribute. - - .. versionadded:: 1.2.0 - - :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` - and defaults to `(0, 2)`. - """ - - shadow_color = ColorProperty([0, 0, 0, 0.6]) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_color` - attribute. - - .. versionadded:: 1.2.0 - - :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.6]`. - """ - - rows_num = NumericProperty(5) - """ - The number of rows displayed on one page of the table. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-use-pagination-rows-num.png - :align: center - - :attr:`rows_num` is an :class:`~kivy.properties.NumericProperty` - and defaults to `10`. - """ - - pagination_menu_pos = OptionProperty( - "top", options=["center", "auto", "top"] - ) - """ - Menu position for selecting the number of displayed rows. - Available options are `'center'`, `'auto'`. - - .. rubric:: Center - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-top.png - :align: center - - .. rubric:: Auto - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-pos-auto.png - :align: center - - :attr:`pagination_menu_pos` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'center'`. - """ - - pagination_menu_height = NumericProperty("140dp") - """ - Menu height for selecting the number of displayed rows. - - .. rubric:: 240dp - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-menu-height-240.png - :align: center - - :attr:`pagination_menu_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'140dp'`. - """ - - background_color = ColorProperty([0, 0, 0, 0]) - """ - Background color in the format (r, g, b, a) or string format. - See :attr:`~kivy.uix.modalview.ModalView.background_color`. - - Use markup strings - ------------------ - - .. code-block:: python - - from kivy.metrics import dp - from kivy.uix.anchorlayout import AnchorLayout - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - layout = AnchorLayout() - data_tables = MDDataTable( - size_hint=(0.9, 0.6), - use_pagination=True, - column_data=[ - ("No.", dp(30)), - ("Column 1", dp(30)), - ("[color=#52251B]Column 2[/color]", dp(30)), - ("Column 3", dp(30)), - ("[size=24][color=#C042B8]Column 4[/color][/size]", dp(30)), - ("Column 5", dp(30)), - ], - row_data=[ - ( - f"{i + 1}", - "[color=#297B50]1[/color]", - "[color=#C552A1]2[/color]", - "[color=#6C9331]3[/color]", - "4", - "5", - ) - for i in range(50) - ], - ) - layout.add_widget(data_tables) - return layout - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/datatables-use-markup-strings.png - :align: center - - :attr:`background_color` is a :class:`~kivy.properties.ColorProperty` and - defaults to `[0, 0, 0, 0]`. - """ - - background_color_header = ColorProperty(None) - """ - Background color in the format (r, g, b, a) or string format for - :class:`~TableHeader` class. - - .. versionadded:: 1.0.0 - - .. code-block:: python - - self.data_tables = MDDataTable( - ..., - background_color_header="#65275d", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-header.png - :align: center - - :attr:`background_color_header` is a :class:`~kivy.properties.ColorProperty` and - defaults to `None`. - """ - - background_color_cell = ColorProperty(None) - """ - Background color in the format (r, g, b, a) or string format for - :class:`~CellRow` class. - - .. versionadded:: 1.0.0 - - .. code-block:: python - - self.data_tables = MDDataTable( - ..., - background_color_header="#65275d", - background_color_cell="#451938", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-cell.png - :align: center - - :attr:`background_color_cell` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - background_color_selected_cell = ColorProperty(None) - """ - Background selected color in the format (r, g, b, a) or string format for - :class:`~CellRow` class. - - .. versionadded:: 1.0.0 - - .. code-block:: python - - self.data_tables = MDDataTable( - ..., - background_color_header="#65275d", - background_color_cell="#451938", - background_color_selected_cell="e4514f", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-background-color-selected-cell.png - :align: center - - :attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` and - defaults to `None`. - """ - - effect_cls = ObjectProperty(StiffScrollEffect) - """ - Effect class. See ``kivy/effects`` package for more information. - - .. versionadded:: 1.0.0 - - :attr:`effect_cls` is an :class:`~kivy.properties.ObjectProperty` - and defaults to :class:`~kivymd.effects.stiffscroll.StiffScrollEffect`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.header = TableHeader( - column_data=self.column_data, - sorted_on=self.sorted_on, - sorted_order=self.sorted_order, - background_color_header=self.background_color_header, - ) - self.table_data = TableData( - self.header, - row_data=self.row_data, - check=self.check, - rows_num=self.rows_num, - _parent=self, - ) - self.register_event_type("on_row_press") - self.register_event_type("on_check_press") - self.pagination = TablePagination(table_data=self.table_data) - self.table_data.pagination = self.pagination - self.header.table_data = self.table_data - self.table_data.fbind("scroll_x", self._scroll_with_header) - self.ids.container.add_widget(self.header) - self.ids.container.add_widget(self.table_data) - if self.use_pagination: - self.ids.container.add_widget(self.pagination) - Clock.schedule_once(self.create_pagination_menu, 0.5) - self.bind(row_data=self.update_row_data) - - def update_row_data(self, instance_data_table, data: list) -> None: - """ - Called when a the widget data must be updated. - - Remember that this is a heavy function. since the whole data set must - be updated. you can get better results calling this metod with in a - coroutine. - """ - - self.table_data.row_data = data - self.row_data = data - self.table_data.on_rows_num(self, self.table_data.rows_num) - # Set cursors to 0. - self.table_data._rows_number = 0 - self.table_data._current_value = 1 - - if len(data) < self.table_data.rows_num: - self.table_data._to_value = len(data) - self.table_data.pagination.ids.button_forward.disabled = True - else: - self.table_data._to_value = self.table_data.rows_num - self.table_data.pagination.ids.button_forward.disabled = False - - self.table_data.set_next_row_data_parts("") - self.pagination.ids.button_back.disabled = True - if self.use_pagination: - Clock.schedule_once(self.create_pagination_menu, 0.5) - - def add_row(self, data: Union[list, tuple]) -> None: - """ - Added new row to common table. - Argument `data` is the row data from the list :attr:`row_data`. - - .. rubric:: Add/remove row - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.button import MDRaisedButton - - - class Example(MDApp): - data_tables = None - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - layout = MDFloatLayout() # root layout - # Creating control buttons. - button_box = MDBoxLayout( - pos_hint={"center_x": 0.5}, - adaptive_size=True, - padding="24dp", - spacing="24dp", - ) - - for button_text in ["Add row", "Remove row"]: - button_box.add_widget( - MDRaisedButton( - text=button_text, on_release=self.on_button_press - ) - ) - - # Create a table. - self.data_tables = MDDataTable( - pos_hint={"center_y": 0.5, "center_x": 0.5}, - size_hint=(0.9, 0.6), - use_pagination=False, - column_data=[ - ("No.", dp(30)), - ("Column 1", dp(40)), - ("Column 2", dp(40)), - ("Column 3", dp(40)), - ], - row_data=[("1", "1", "2", "3")], - ) - # Adding a table and buttons to the toot layout. - layout.add_widget(self.data_tables) - layout.add_widget(button_box) - - return layout - - def on_button_press(self, instance_button: MDRaisedButton) -> None: - '''Called when a control button is clicked.''' - - try: - { - "Add row": self.add_row, - "Remove row": self.remove_row, - }[instance_button.text]() - except KeyError: - pass - - def add_row(self) -> None: - last_num_row = int(self.data_tables.row_data[-1][0]) - self.data_tables.add_row((str(last_num_row + 1), "1", "2", "3")) - - def remove_row(self) -> None: - if len(self.data_tables.row_data) > 1: - self.data_tables.remove_row(self.data_tables.row_data[-1]) - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-add-remove-row.gif - :align: center - - Deleting checked rows - --------------------- - - .. code-block:: python - - from kivy.metrics import dp - from kivy.lang import Builder - from kivy.clock import Clock - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivymd.uix.screen import MDScreen - - KV = ''' - MDBoxLayout: - orientation: "vertical" - padding: "56dp" - spacing: "24dp" - - MDData: - id: table_screen - - MDRaisedButton: - text: "DELETE CHECKED ROWS" - on_release: table_screen.delete_checked_rows() - ''' - - - class MDData(MDScreen): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.data = [ - ["1", "Asep Sudrajat", "Male", "Soccer"], - ["2", "Egy", "Male", "Soccer"], - ["3", "Tanos", "Demon", "Soccer"], - ] - self.data_tables = MDDataTable( - use_pagination=True, - check=True, - column_data=[ - ("No", dp(30)), - ("No Urut.", dp(30)), - ("Alamat Pengirim", dp(30)), - ("No Surat", dp(60)), - ] - ) - self.data_tables.row_data = self.data - self.add_widget(self.data_tables) - - def delete_checked_rows(self): - def deselect_rows(*args): - self.data_tables.table_data.select_all("normal") - - for data in self.data_tables.get_row_checks(): - self.data_tables.remove_row(data) - - Clock.schedule_once(deselect_rows) - - - class MyApp(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - - MyApp().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-deleting-checked-rows.gif - :align: center - - .. versionadded:: 1.0.0 - """ - - self.row_data.append(data) - - def remove_row(self, data: Union[list, tuple]) -> None: - """ - Removed row from common table. - Argument `data` is the row data from the list :attr:`row_data`. - - See the code in the doc string for the :attr:`add_row` method for more - information. - - .. versionadded:: 1.0.0 - """ - - self.row_data.remove(data) - - def update_row( - self, old_data: Union[list, tuple], new_data: Union[list, tuple] - ) -> None: - """ - Updates a table row. - Argument `old_data/new_data` is the row data from the list :attr:`row_data`. - - .. rubric:: Update row - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.datatables import MDDataTable - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.button import MDRaisedButton - - - class Example(MDApp): - data_tables = None - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - - layout = MDFloatLayout() - layout.add_widget( - MDRaisedButton( - text="Change 2 row", - pos_hint={"center_x": 0.5}, - on_release=self.update_row, - y=24, - ) - ) - self.data_tables = MDDataTable( - pos_hint={"center_y": 0.5, "center_x": 0.5}, - size_hint=(0.9, 0.6), - use_pagination=False, - column_data=[ - ("No.", dp(30)), - ("Column 1", dp(40)), - ("Column 2", dp(40)), - ("Column 3", dp(40)), - ], - row_data=[(f"{i + 1}", "1", "2", "3") for i in range(3)], - ) - layout.add_widget(self.data_tables) - - return layout - - def update_row(self, instance_button: MDRaisedButton) -> None: - self.data_tables.update_row( - self.data_tables.row_data[1], # old row data - ["2", "A", "B", "C"], # new row data - ) - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/data-tables-change-row.gif - :align: center - - .. versionadded:: 1.0.0 - """ - - for data in self.row_data: - if data == old_data: - index_data = self.row_data.index(data) - self.row_data[index_data] = new_data - break - - def on_row_press(self, instance_cell_row) -> None: - """Called when a table row is clicked.""" - - def on_check_press(self, row_data: list) -> None: - """ - Called when the check box in the table row is checked. - - :param row_data: One of the elements from the :attr:`MDDataTable.row_data` list. - """ - - def get_row_checks(self) -> list: - """Returns all rows that are checked.""" - - return self.table_data._get_row_checks() - - def create_pagination_menu(self, interval: Union[int, float]) -> None: - menu_items = [ - { - "text": f"{i}", - "viewclass": "OneLineListItem", - "height": dp(56), - "on_release": lambda x=f"{i}": self.table_data.set_number_displayed_lines( - x - ), - } - for i in range(self.rows_num, len(self.row_data), self.rows_num) - ] - pagination_menu = MDDropdownMenu( - caller=self.pagination.ids.drop_item, - items=menu_items, - position=self.pagination_menu_pos, - max_height=self.pagination_menu_height, - width_mult=2, - ) - pagination_menu.bind( - on_dismiss=self.table_data.close_pagination_menu, - ) - self.table_data.pagination_menu = pagination_menu - - def _scroll_with_header(self, instance, value): - self.header.scroll_x = value - - -class CellRow( - RecycleDataViewBehavior, - HoverBehavior, - ButtonBehavior, - MDBoxLayout, -): - """Implements a data row from :attr:`~MDDataTable.column_data`.""" - - background_color_cell = ColorProperty(None) - """ - See :attr:`~MDDataTable.background_color_cell.`. - - .. versionadded:: 1.0.0 - - :attr:`background_color_cell` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - background_color_selected_cell = ColorProperty(None) - """ - See :attr:`~MDDataTable.background_color_selected_cell.`. - - .. versionadded:: 1.0.0 - - :attr:`background_color_selected_cell` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text = StringProperty() - """ - Row text. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - table = ObjectProperty() - """ - Class class:`~TableData`. - - :attr:`table` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - icon = StringProperty() - """ - Row icon name. - - :attr:`icon` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_color = ColorProperty(None) - """ - Row icon color. - - :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - index = None - icon_copy = icon - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.ids.check.bind(active=self.select_check) - self.ids.check.bind(active=self.notify_checkbox_click) - - def notify_checkbox_click( - self, instance_check: MDCheckbox, active: bool - ) -> None: - """Called when the table row checkbox is activated/deactivated.""" - - self.table.get_select_row(self.index) - - def refresh_view_attrs( - self, instance_table_data: TableData, index: int, data: dict - ): - """ - Called by the :class:`RecycleAdapter` when the view is initially - populated with the values from the `data` dictionary for this item. - - Any pos or size info should be removed because they are set - subsequently with :attr:`refresh_view_layout`. - - :Parameters: - - `table_data`: :class:`TableData` instance - The :class:`TableData` that caused the update. - `data`: dict - The data dict used to populate this view. - """ - - self.index = index - return super().refresh_view_attrs(instance_table_data, index, data) - - def apply_selection( - self, instance_table_data: TableData, index: int, is_selected: bool - ) -> None: - """Called when list items of table appear on the screen.""" - - self.selected = is_selected - - # Fixes cloning of icons. - ic = instance_table_data.recycle_data[index].get("icon", None) - cell_row_obj = instance_table_data.view_adapter.get_visible_view(index) - - if not ic: - cell_row_obj.icon = "" - else: - cell_row_obj.icon = cell_row_obj.icon_copy - - # Set checkboxes. - if instance_table_data.check: - if self.index in instance_table_data.data_first_cells: - self.ids.check.size = (dp(32), dp(32)) - self.ids.check.opacity = 1 - self.ids.box.spacing = dp(16) - self.ids.box.padding[0] = dp(8) - else: - self.ids.check.size = (0, 0) - self.ids.check.opacity = 0 - self.ids.box.spacing = 0 - self.ids.box.padding[0] = 0 - - # Set checkboxes state. - if ( - instance_table_data._rows_number - in instance_table_data.current_selection_check - ): - for index in instance_table_data.current_selection_check[ - instance_table_data._rows_number - ]: - if ( - self.index - in instance_table_data.current_selection_check[ - instance_table_data._rows_number - ] - ): - self.change_check_state_no_notify("down") - else: - self.change_check_state_no_notify("normal") - else: - self.change_check_state_no_notify("normal") - - def change_check_state_no_notify(self, new_state: str) -> None: - checkbox = self.ids.check - checkbox.unbind(active=self.notify_checkbox_click) - checkbox.state = new_state - checkbox.bind(active=self.notify_checkbox_click) - - def select_check( - self, instance_table_data: MDDataTable, active: bool - ) -> None: - """Called upon activation/deactivation of the checkbox.""" - - if active: - if ( - self.table._rows_number - not in self.table.current_selection_check - ): - self.table.current_selection_check[self.table._rows_number] = [] - if ( - self.index - not in self.table.current_selection_check[ - self.table._rows_number - ] - ): - self.table.current_selection_check[ - self.table._rows_number - ].append(self.index) - else: - if self.table._rows_number in self.table.current_selection_check: - if ( - self.index - in self.table.current_selection_check[ - self.table._rows_number - ] - and not active - ): - self.table.current_selection_check[ - self.table._rows_number - ].remove(self.index) - - def on_touch_down(self, touch): - if super().on_touch_down(touch): - if self.table._parent: - self.table._parent.dispatch("on_row_press", self) - return True - - def on_icon(self, instance_cell_row, name_icon: str) -> None: - self.icon_copy = name_icon - - def on_table( - self, instance_cell_row, instance_table_data: TableData - ) -> None: - """Sets padding/spacing to zero if no checkboxes are used for rows.""" - - if not instance_table_data.check: - self.ids.box.padding = 0 - self.ids.box.spacing = 0 - - def _check_all(self, state): - """Checks if all checkboxes are in same state.""" - - if state == "down" and self.table.check_all(state): - self.table.table_header.ids.check.state = "down" - else: - self.table.table_header.ids.check.state = "normal" - - -class SortButton(MDIconButton): - """Implements a sort button in the :class:`~CellHeader` class.""" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__init__.py index 40adbe4..9fc91de 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__init__.py @@ -1 +1,8 @@ -from .dialog import BaseDialog, MDDialog # NOQA F401 +from .dialog import ( + MDDialog, + MDDialogIcon, + MDDialogHeadlineText, + MDDialogSupportingText, + MDDialogContentContainer, + MDDialogButtonContainer, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/__init__.cpython-311.pyc index 4668b8d..d27038f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/dialog.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/dialog.cpython-311.pyc index 03ecd8e..4a2d581 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/dialog.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/__pycache__/dialog.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.kv index 96e01a3..42eb3ae 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.kv @@ -1,90 +1,114 @@ -#:import images_path kivymd.images_path - - - - background: '{}/transparent.png'.format(images_path) - - canvas.before: - PushMatrix - RoundedRectangle: - pos: self.pos - size: self.size - radius: root.radius - Scale: - origin: self.center - x: root._scale_x - y: root._scale_y - canvas.after: - PopMatrix - - - - shadow_color: 0.0, 0.0, 0.0, 0.0 - elevation: 0 - shadow_softness: 0 - shadow_offset: 0, 0 - - + radius: root.radius + size_hint_min_x: "280dp" + size_hint_max_x: "560dp" + pos_hint: {"center_x": .5, "center_y": .5} + size_hint_y: None + height: container.height + theme_shadow_color: "Custom" + shadow_color: self.theme_cls.transparentColor + focus_behavior: False - DialogContainer: - id: container - orientation: "vertical" + RelativeLayout: size_hint_y: None - height: self.minimum_height - padding: "24dp", "24dp", "8dp", "8dp" - radius: root.radius - md_bg_color: - root.theme_cls.bg_dark \ - if not root.md_bg_color else root.md_bg_color - - MDLabel: - id: title - text: root.title - font_style: "H6" - bold: True - markup: True - size_hint_y: None - height: self.texture_size[1] - valign: "top" + height: container.height BoxLayout: - id: spacer_top_box + id: container + orientation: "vertical" size_hint_y: None - height: root._spacer_top + height: self.minimum_height + button_container.height + dp(24) + spacing: "16dp" + padding: "24dp" + y: button_container.height + dp(24) - MDLabel: - id: text - text: root.text - font_style: "Body1" - theme_text_color: "Custom" - text_color: root.theme_cls.disabled_hint_text_color - size_hint_y: None - height: self.texture_size[1] - markup: True + AnchorLayout: + id: icon_container + size_hint_y: None + anchor_x: "center" + height: self.children[0].height if self.children else 0 - ScrollView: - id: scroll - size_hint_y: None - height: root._scroll_height + BoxLayout: + id: headline_container + size_hint_y: None + height: self.minimum_height - MDGridLayout: - id: box_items - adaptive_height: True - cols: 1 + BoxLayout: + id: supporting_text_container + size_hint_y: None + height: self.minimum_height + + BoxLayout: + id: content_container + size_hint_y: None + height: self.minimum_height BoxLayout: - id: spacer_bottom_box + id: button_container size_hint_y: None height: self.minimum_height + y: content_container.y - AnchorLayout: - id: root_button_box - size_hint_y: None - height: "52dp" - anchor_x: "right" - MDBoxLayout: - id: button_box - adaptive_size: True - spacing: "8dp" + + size_hint: None, None + size: "24dp", "24dp" + theme_font_size: "Custom" + font_size: "24sp" + icon_color: + self.theme_cls.secondaryColor \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color \ + if self.icon_color else self.theme_cls.transparentColor \ + ) + + + + adaptive_height: True + halign: "center" + font_style: "Headline" + role: "small" + markup: True + color: + self.theme_cls.onSurfaceColor \ + if self.theme_text_color == "Primary" else ( \ + self.text_color \ + if self.text_color != self.theme_cls.transparentColor else \ + self.theme_cls.onSurfaceColor \ + ) + + + + adaptive_height: True + halign: "center" + font_style: "Body" + role: "medium" + markup: True + text_color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_text_color == "Primary" else ( \ + self.text_color \ + if self.text_color != self.theme_cls.transparentColor else \ + self.theme_cls.onSurfaceVariantColor \ + ) + + + + size_hint_y: None + height: self.minimum_height + + + + size_hint_y: None + height: self.minimum_height + padding: "24dp", 0, "24dp", 0 + + + + canvas: + Color: + rgba: self.color[:-1] + [self.alpha] + Rectangle: + pos: self.pos + size: self.size diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.py index 9f2db53..1eddfa1 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dialog/dialog.py @@ -4,182 +4,156 @@ Components/Dialog .. seealso:: - `Material Design spec, Dialogs `_ + `Material Design spec, Dialogs `_ -.. rubric:: Dialogs inform users about a task and can contain critical - information, require decisions, or involve multiple tasks. +.. rubric:: Dialogs provide important prompts in a user flow. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialogs.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-preview.png :align: center -Usage ------ +- Use dialogs to make sure users act on information +- Two types: basic and full-screen (full-screen not provided in KivyMD) +- Should be dedicated to completing a single task +- Can also display information relevant to the task +- Commonly used to confirm high-risk actions like deleting progress + +Anatomy +======= + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-anatomy.png + :align: center + +Example +======= .. code-block:: python from kivy.lang import Builder + from kivy.uix.widget import Widget + + from kivymd.app import MDApp + from kivymd.uix.button import MDButton, MDButtonText + from kivymd.uix.dialog import ( + MDDialog, + MDDialogIcon, + MDDialogHeadlineText, + MDDialogSupportingText, + MDDialogButtonContainer, + MDDialogContentContainer, + ) + from kivymd.uix.divider import MDDivider + from kivymd.uix.list import ( + MDListItem, + MDListItemLeadingIcon, + MDListItemSupportingText, + ) + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_alert_dialog() + + MDButtonText: + text: "Show dialog" + ''' + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + def show_alert_dialog(self): + MDDialog( + # ----------------------------Icon----------------------------- + MDDialogIcon( + icon="refresh", + ), + # -----------------------Headline text------------------------- + MDDialogHeadlineText( + text="Reset settings?", + ), + # -----------------------Supporting text----------------------- + MDDialogSupportingText( + text="This will reset your app preferences back to their " + "default settings. The following accounts will also " + "be signed out:", + ), + # -----------------------Custom content------------------------ + MDDialogContentContainer( + MDDivider(), + MDListItem( + MDListItemLeadingIcon( + icon="gmail", + ), + MDListItemSupportingText( + text="KivyMD-library@yandex.com", + ), + theme_bg_color="Custom", + md_bg_color=self.theme_cls.transparentColor, + ), + MDListItem( + MDListItemLeadingIcon( + icon="gmail", + ), + MDListItemSupportingText( + text="kivydevelopment@gmail.com", + ), + theme_bg_color="Custom", + md_bg_color=self.theme_cls.transparentColor, + ), + MDDivider(), + orientation="vertical", + ), + # ---------------------Button container------------------------ + MDDialogButtonContainer( + Widget(), + MDButton( + MDButtonText(text="Cancel"), + style="text", + ), + MDButton( + MDButtonText(text="Accept"), + style="text", + ), + spacing="8dp", + ), + # ------------------------------------------------------------- + ).open() + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-example.gif + :align: center + +.. warning:: Do not try to use the MDDialog widget in KV files. + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + from kivy.uix.widget import Widget from kivymd.app import MDApp from kivymd.uix.button import MDFlatButton from kivymd.uix.dialog import MDDialog - KV = ''' - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_alert_dialog() - ''' - class Example(MDApp): - dialog = None - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) + return Widget() - def show_alert_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - text="Discard draft?", - buttons=[ - MDFlatButton( - text="CANCEL", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - MDFlatButton( - text="DISCARD", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - ], - ) - self.dialog.open() - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/alert-dialog.png - :align: center -""" - -__all__ = ("MDDialog", "BaseDialog") - -import os - -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.modalview import ModalView - -from kivymd import uix_path -from kivymd.material_resources import DEVICE_TYPE -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import CommonElevationBehavior, MotionDialogBehavior -from kivymd.uix.button import BaseButton -from kivymd.uix.card import MDSeparator -from kivymd.uix.list import BaseListItem - -with open( - os.path.join(uix_path, "dialog", "dialog.kv"), encoding="utf-8" -) as kv_file: - Builder.load_string(kv_file.read()) - - -class BaseDialog( - ThemableBehavior, MotionDialogBehavior, ModalView, CommonElevationBehavior -): - elevation = NumericProperty(3) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation` - attribute for more information. - - .. versionadded:: 1.1.0 - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `3`. - """ - - shadow_softness = NumericProperty(24) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness` - attribute for more information. - - .. versionadded:: 1.1.0 - - :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` - and defaults to `24`. - """ - - shadow_offset = ListProperty((0, 4)) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset` - attribute for more information. - - .. versionadded:: 1.1.0 - - :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 4]`. - """ - - radius = ListProperty([dp(7), dp(7), dp(7), dp(7)]) - """ - Dialog corners rounding value. - - .. code-block:: python - - [...] - self.dialog = MDDialog( - text="Oops! Something seems to have gone wrong!", - radius=[20, 7, 20, 7], - ) - [...] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-radius.png - :align: center - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[7, 7, 7, 7]`. - """ - - _scale_x = NumericProperty(1) - _scale_y = NumericProperty(1) - - -class MDDialog(BaseDialog): - """ - Dialog class. - - For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.modalview.ModalView` and - :class:`~kivymd.uix.behaviors.CommonElevationBehavior` - classes documentation. - """ - - title = StringProperty() - """ - Title dialog. - - .. code-block:: python - - [...] - self.dialog = MDDialog( - title="Reset settings?", + def on_start(self): + MDDialog( + title="Discard draft?", buttons=[ MDFlatButton( text="CANCEL", @@ -187,30 +161,35 @@ class MDDialog(BaseDialog): text_color=self.theme_cls.primary_color, ), MDFlatButton( - text="ACCEPT", + text="DISCARD", theme_text_color="Custom", text_color=self.theme_cls.primary_color, ), ], - ) - [...] + ).open() - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-title.png - :align: center - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ + Example().run() - text = StringProperty() - """ - Text dialog. +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-api-break-1-2-0.png + :align: center - .. code-block:: python +.. code-block:: python - [...] - self.dialog = MDDialog( - title="Reset settings?", + from kivy.uix.widget import Widget + + from kivymd.app import MDApp + from kivymd.uix.button import MDFlatButton + from kivymd.uix.dialog import MDDialog + + + class Example(MDApp): + def build(self): + return Widget() + + def on_start(self): + MDDialog( + title="Discard draft?", text="This will reset your device to its default factory settings.", buttons=[ MDFlatButton( @@ -219,198 +198,267 @@ class MDDialog(BaseDialog): text_color=self.theme_cls.primary_color, ), MDFlatButton( - text="ACCEPT", + text="DISCARD", theme_text_color="Custom", text_color=self.theme_cls.primary_color, ), ], - ) - [...] + ).open() - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-text.png - :align: center - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ + Example().run() - buttons = ListProperty() - """ - List of button objects for dialog. - Objects must be inherited from :class:`~kivymd.uix.button.BaseButton` class. +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/2-dialog-api-break-1-2-0.png + :align: center - .. code-block:: python +.. code-block:: python - [...] - self.dialog = MDDialog( - text="Discard draft?", - buttons=[ - MDFlatButton(text="CANCEL"), MDRaisedButton(text="DISCARD"), + from kivy.lang import Builder + from kivy.properties import StringProperty + from kivy.uix.widget import Widget + + from kivymd import images_path + from kivymd.app import MDApp + from kivymd.uix.dialog import MDDialog + from kivymd.uix.list import OneLineAvatarListItem + + KV = ''' + + + ImageLeftWidget: + source: root.source + ''' + + + class Item(OneLineAvatarListItem): + divider = None + source = StringProperty() + + + class Example(MDApp): + def build(self): + Builder.load_string(KV) + return Widget() + + def on_start(self): + MDDialog( + title="Set backup account", + type="simple", + items=[ + Item(text="user01@gmail.com", source=f"{images_path}/logo/kivymd-icon-128.png"), + Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"), ], - ) - [...] + ).open() - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-buttons.png - :align: center - :attr:`buttons` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/3-dialog-api-break-1-2-0.png + :align: center + +2.2.0 version +------------- + +.. code-block:: python + + from kivy.uix.widget import Widget + + from kivymd.uix.widget import MDWidget + from kivymd.app import MDApp + from kivymd.uix.button import MDButton, MDButtonText + from kivymd.uix.dialog import MDDialog, MDDialogHeadlineText, MDDialogButtonContainer + + + class Example(MDApp): + def build(self): + return MDWidget(md_bg_color=self.theme_cls.backgroundColor) + + def on_start(self): + MDDialog( + MDDialogHeadlineText( + text="Discard draft?", + halign="left", + ), + MDDialogButtonContainer( + Widget(), + MDButton( + MDButtonText(text="Cancel"), + style="text", + ), + MDButton( + MDButtonText(text="Discard"), + style="text", + ), + spacing="8dp", + ), + ).open() + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-api-break-2-2-0.png + :align: center + +.. code-block:: python + + from kivy.uix.widget import Widget + + from kivymd.uix.widget import MDWidget + from kivymd.app import MDApp + from kivymd.uix.button import MDButton, MDButtonText + from kivymd.uix.dialog import MDDialog, MDDialogHeadlineText, MDDialogButtonContainer + + + class Example(MDApp): + def build(self): + return MDWidget(md_bg_color=self.theme_cls.backgroundColor) + + def on_start(self): + MDDialog( + MDDialogHeadlineText( + text="Discard draft?", + halign="left", + ), + MDDialogSupportingText( + text="This will reset your device to its default factory settings.", + halign="left", + ), + MDDialogButtonContainer( + Widget(), + MDButton( + MDButtonText(text="Cancel"), + style="text", + ), + MDButton( + MDButtonText(text="Discard"), + style="text", + ), + spacing="8dp", + ), + ).open() + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/2-dialog-api-break-2-2-0.png + :align: center + +.. code-block:: python + + from kivymd import images_path + from kivymd.uix.widget import MDWidget + from kivymd.app import MDApp + from kivymd.uix.dialog import ( + MDDialog, + MDDialogHeadlineText, + MDDialogContentContainer, + ) + from kivymd.uix.list import ( + MDListItem, + MDListItemLeadingAvatar, + MDListItemSupportingText, + ) + + + class Example(MDApp): + def build(self): + return MDWidget(md_bg_color=self.theme_cls.backgroundColor) + + def on_start(self): + MDDialog( + MDDialogHeadlineText( + text="Set backup account", + halign="left", + ), + MDDialogContentContainer( + MDListItem( + MDListItemLeadingAvatar( + source=f"{images_path}/logo/kivymd-icon-128.png", + ), + MDListItemSupportingText( + text="user01@gmail.com", + ), + theme_bg_color="Custom", + md_bg_color=self.theme_cls.transparentColor, + ), + MDListItem( + MDListItemLeadingAvatar( + source="data/logo/kivy-icon-128.png", + ), + MDListItemSupportingText( + text="user01@gmail.com", + ), + theme_bg_color="Custom", + md_bg_color=self.theme_cls.transparentColor, + ), + orientation="vertical", + ), + ).open() + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/3-dialog-api-break-2-2-0.png + :align: center + +""" + +__all__ = [ + "MDDialog", + "MDDialogIcon", + "MDDialogHeadlineText", + "MDDialogSupportingText", + "MDDialogContentContainer", + "MDDialogButtonContainer", +] + +import os + +from kivy.core.window import Window +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import ( + VariableListProperty, + NumericProperty, + ColorProperty, + ObjectProperty, + BooleanProperty, +) +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.widget import Widget + +from kivymd.uix.card import MDCard +from kivymd.uix.label import MDIcon, MDLabel +from kivymd import uix_path +from kivymd.material_resources import DEVICE_TYPE +from kivymd.uix.behaviors import MotionDialogBehavior, DeclarativeBehavior + +with open( + os.path.join(uix_path, "dialog", "dialog.kv"), encoding="utf-8" +) as kv_file: + Builder.load_string(kv_file.read()) + + +class MDDialog(MDCard, MotionDialogBehavior): """ + Dialog class. - items = ListProperty() - """ - List of items objects for dialog. - Objects must be inherited from :class:`~kivymd.uix.list.BaseListItem` class. + For more information, see in the + :class:`~kivymd.uix.card.card.MDCard` and + :class:`~kivymd.uix.behaviors.motion_behavior.MotionDialogBehavior` + classes documentation. - With type 'simple' - ~~~~~~~~~~~~~~~~~~ - - .. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.dialog import MDDialog - from kivymd.uix.list import OneLineAvatarListItem - - KV = ''' - - - ImageLeftWidget: - source: root.source - - - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_simple_dialog() - ''' - - - class Item(OneLineAvatarListItem): - divider = None - source = StringProperty() - - - class Example(MDApp): - dialog = None - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def show_simple_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Set backup account", - type="simple", - items=[ - Item(text="user01@gmail.com", source="kivymd/images/logo/kivymd-icon-128.png"), - Item(text="user02@gmail.com", source="data/logo/kivy-icon-128.png"), - ], - ) - self.dialog.open() - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-items.png - :align: center - - With type 'confirmation' - ~~~~~~~~~~~~~~~~~~~~~~~~ - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog - from kivymd.uix.list import OneLineAvatarIconListItem - - KV = ''' - - on_release: root.set_icon(check) - - CheckboxLeftWidget: - id: check - group: "check" - - - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_confirmation_dialog() - ''' - - - class ItemConfirm(OneLineAvatarIconListItem): - divider = None - - def set_icon(self, instance_check): - instance_check.active = True - check_list = instance_check.get_widgets(instance_check.group) - for check in check_list: - if check != instance_check: - check.active = False - - - class Example(MDApp): - dialog = None - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def show_confirmation_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Phone ringtone", - type="confirmation", - items=[ - ItemConfirm(text="Callisto"), - ItemConfirm(text="Luna"), - ItemConfirm(text="Night"), - ItemConfirm(text="Solo"), - ItemConfirm(text="Phobos"), - ItemConfirm(text="Diamond"), - ItemConfirm(text="Sirena"), - ItemConfirm(text="Red music"), - ItemConfirm(text="Allergio"), - ItemConfirm(text="Magic"), - ItemConfirm(text="Tic-tac"), - ], - buttons=[ - MDFlatButton( - text="CANCEL", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - MDFlatButton( - text="OK", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - ], - ) - self.dialog.open() - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-confirmation.png - :align: center - - :attr:`items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. + :Events: + `on_pre_open`: + Fired before the MDDialog is opened. When this event is fired + MDDialog is not yet added to window. + `on_open`: + Fired when the MDDialog is opened. + `on_pre_dismiss`: + Fired before the MDDialog is closed. + `on_dismiss`: + Fired when the MDDialog is closed. If the callback returns True, + the dismiss will be canceled. """ width_offset = NumericProperty(dp(48)) @@ -421,270 +469,160 @@ class MDDialog(BaseDialog): and defaults to `dp(48)`. """ - type = OptionProperty( - "alert", options=["alert", "simple", "confirmation", "custom"] - ) + radius = VariableListProperty(dp(28), lenght=4) """ - Dialog type. - Available option are `'alert'`, `'simple'`, `'confirmation'`, `'custom'`. + Dialog corners rounding value. - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'alert'`. + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(28), dp(28), dp(28), dp(28)]`. """ - content_cls = ObjectProperty() + scrim_color = ColorProperty([0, 0, 0, 0.5]) """ - Custom content class. This attribute is only available when :attr:`type` is - set to `'custom'`. + Color for scrim in (r, g, b, a) or string format. - .. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - from kivy.uix.boxlayout import BoxLayout - - from kivymd.app import MDApp - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog - - KV = ''' - - orientation: "vertical" - spacing: "12dp" - size_hint_y: None - height: "120dp" - - MDTextField: - hint_text: "City" - - MDTextField: - hint_text: "Street" - - - MDFloatLayout: - - MDFlatButton: - text: "ALERT DIALOG" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_confirmation_dialog() - ''' - - - class Content(BoxLayout): - pass - - - class Example(MDApp): - dialog = None - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def show_confirmation_dialog(self): - if not self.dialog: - self.dialog = MDDialog( - title="Address:", - type="custom", - content_cls=Content(), - buttons=[ - MDFlatButton( - text="CANCEL", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - MDFlatButton( - text="OK", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - ], - ) - self.dialog.open() - - - Example().run() - - .. tab:: Declarative Python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDFlatButton - from kivymd.uix.dialog import MDDialog - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.textfield import MDTextField - - - class Example(MDApp): - dialog = None - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDFloatLayout( - MDFlatButton( - text="ALERT DIALOG", - pos_hint={'center_x': 0.5, 'center_y': 0.5}, - on_release=self.show_confirmation_dialog, - ) - ) - ) - - def show_confirmation_dialog(self, *args): - if not self.dialog: - self.dialog = MDDialog( - title="Address:", - type="custom", - content_cls=MDBoxLayout( - MDTextField( - hint_text="City", - ), - MDTextField( - hint_text="Street", - ), - orientation="vertical", - spacing="12dp", - size_hint_y=None, - height="120dp", - ), - buttons=[ - MDFlatButton( - text="CANCEL", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - MDFlatButton( - text="OK", - theme_text_color="Custom", - text_color=self.theme_cls.primary_color, - ), - ], - ) - self.dialog.open() - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/dialog-custom.png - :align: center - - :attr:`content_cls` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `'None'`. + :attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0.5]`. """ - md_bg_color = ColorProperty(None) + auto_dismiss = BooleanProperty(True) """ - Background color in the (r, g, b, a) or string format. + This property determines if the dialog is automatically + dismissed when the user clicks outside it. - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. + ..versionadded:: 2.0.0 + + :attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty` and + defaults to True. """ - _scroll_height = NumericProperty("28dp") - _spacer_top = NumericProperty("24dp") + _scrim = ObjectProperty() # kivymd.uix.dialog.dialog.MDDialogScrim object + _is_open = False # is the dialog currently open or closed. - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_event_type("on_open") + self.register_event_type("on_pre_open") + self.register_event_type("on_dismiss") + self.register_event_type("on_pre_dismiss") + self.opacity = 0 Window.bind(on_resize=self.update_width) - if self.size_hint == [1, 1] and ( - DEVICE_TYPE == "desktop" or DEVICE_TYPE == "tablet" - ): - self.size_hint = (None, None) - self.width = min(dp(560), Window.width - self.width_offset) - elif self.size_hint == [1, 1] and DEVICE_TYPE == "mobile": - self.size_hint = (None, None) - self.width = min(dp(280), Window.width - self.width_offset) - - if not self.title: - self._spacer_top = 0 - - if not self.buttons: - self.ids.root_button_box.height = 0 - else: - self.create_buttons() - - update_height = False - if self.type in ("simple", "confirmation"): - if self.type == "confirmation": - self.ids.spacer_top_box.add_widget(MDSeparator()) - self.ids.spacer_bottom_box.add_widget(MDSeparator()) - self.create_items() - if self.type == "custom": - if self.content_cls: - self.ids.container.remove_widget(self.ids.scroll) - self.ids.container.remove_widget(self.ids.text) - self.ids.spacer_top_box.add_widget(self.content_cls) - self.ids.spacer_top_box.padding = (0, "24dp", "16dp", 0) - update_height = True - if self.type == "alert": - self.ids.scroll.bar_width = 0 - - if update_height: - Clock.schedule_once(self.update_height) - def update_width(self, *args) -> None: - self.width = max( - self.height + self.width_offset, + self.size_hint_max_x = max( + self.width_offset, min( dp(560) if DEVICE_TYPE != "mobile" else dp(280), Window.width - self.width_offset, ), ) - def update_height(self, *args) -> None: - self._spacer_top = self.content_cls.height + dp(24) + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDDialogIcon): + self.ids.icon_container.add_widget(widget) + elif isinstance(widget, MDDialogHeadlineText): + self.ids.headline_container.add_widget(widget) + elif isinstance(widget, MDDialogSupportingText): + self.ids.supporting_text_container.add_widget(widget) + elif isinstance(widget, MDDialogContentContainer): + self.ids.content_container.add_widget(widget) + elif isinstance(widget, MDDialogButtonContainer): + self.ids.button_container.add_widget(widget) + else: + return super().add_widget(widget) - def update_items(self, items: list) -> None: - self.ids.box_items.clear_widgets() - self.items = items - self.create_items() + def open(self) -> None: + """Show the dialog.""" - def on_open(self) -> None: - # TODO: Add scrolling text. - self.height = self.ids.container.height + if self._is_open: + return + + self.dispatch("on_pre_open") + self._is_open = True + + if not self._scrim: + self._scrim = MDDialogScrim(color=self.scrim_color) + + Window.add_widget(self._scrim) + Window.add_widget(self) super().on_open() + self.dispatch("on_open") - def get_normal_height(self) -> float: - return ( - (Window.height * 80 / 100) - - self._spacer_top - - dp(52) - - self.ids.container.padding[1] - - self.ids.container.padding[-1] - - 100 - ) + def on_pre_open(self, *args) -> None: + """Fired when a dialog pre opened.""" - def edit_padding_for_item(self, instance_item) -> None: - instance_item.ids._left_container.x = 0 - instance_item._txt_left_pad = "56dp" + def on_open(self, *args) -> None: + """Fired when a dialog opened.""" - def create_items(self) -> None: - if not self.text: - self.ids.container.remove_widget(self.ids.text) - height = 0 - else: - height = self.ids.text.height + def on_dismiss(self, *args) -> None: + """Fired when a dialog dismiss.""" - for item in self.items: - if issubclass(item.__class__, BaseListItem): - height += item.height # calculate height contents - self.edit_padding_for_item(item) - self.ids.box_items.add_widget(item) + def on_pre_dismiss(self, *args) -> None: + """Fired when a dialog pre-dismiss.""" - if height > Window.height: - self.ids.scroll.height = self.get_normal_height() - else: - self.ids.scroll.height = height + def on_touch_down(self, touch): + if not self.collide_point(*touch.pos) and self.auto_dismiss: + self.dismiss() + return True + super().on_touch_down(touch) + return True - def create_buttons(self) -> None: - for button in self.buttons: - if issubclass(button.__class__, BaseButton): - self.ids.button_box.add_widget(button) + def dismiss(self, *args) -> None: + """Closes the dialog.""" + + self.dispatch("on_pre_dismiss") + super().on_dismiss() + self._is_open = False + self.dispatch("on_dismiss") + + +class MDDialogIcon(MDIcon): + """ + The class implements an icon. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDIcon` class documentation. + """ + + +class MDDialogHeadlineText(MDLabel): + """ + The class implements the headline text. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + +class MDDialogSupportingText(MDLabel): + """ + The class implements the supporting text. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + +class MDDialogContentContainer(DeclarativeBehavior, BoxLayout): + """ + The class implements the container for custom widgets. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation. + """ + + +class MDDialogButtonContainer(DeclarativeBehavior, BoxLayout): + """ + The class implements a container for placing dialog buttons. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation. + """ + + +class MDDialogScrim(Widget): + color = ColorProperty(None) + alpha = NumericProperty(0) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__init__.py new file mode 100644 index 0000000..91a4075 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__init__.py @@ -0,0 +1 @@ +from .divider import MDDivider # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..ce7708e Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__pycache__/divider.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__pycache__/divider.cpython-311.pyc new file mode 100644 index 0000000..3fe3afe Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/__pycache__/divider.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/divider.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/divider.kv new file mode 100644 index 0000000..071ea90 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/divider.kv @@ -0,0 +1,10 @@ + + canvas: + Color: + rgba: + app.theme_cls.outlineVariantColor \ + if not self.color else \ + self.color + Rectangle: + size: self.size + pos: self.pos diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/divider.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/divider.py new file mode 100644 index 0000000..b9c14a3 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/divider/divider.py @@ -0,0 +1,159 @@ +""" +Components/Divider +================== + +.. versionadded:: 2.0.0 + +.. seealso:: + + `Material Design 3 spec, Divider `_ + +.. rubric:: Dividers are thin lines that group content in lists or other containers. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/divider.png + :align: center + +- Make dividers visible but not bold +- Only use dividers if items can’t be grouped with open space +- Use dividers to group things, not separate individual items + +`KivyMD` provides the following bar positions for use: + +- HorizontalDivider_ +- VerticalDivider_ + +.. HorizontalDivider: + +HorizontalDivider +----------------- + +Dividers are one way to visually group components and create hierarchy. +They can also be used to imply nested parent/child relationships. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/divider-horizontal-desc.png + :align: center + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDDivider: + size_hint_x: .5 + pos_hint: {'center_x': .5, 'center_y': .5} + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/divider-horizontal.png + :align: center + +.. VerticalDivider: + +VerticalDivider +--------------- + +A vertical divider can be used to arrange content on a larger screen, such as +separating paragraph text from video or imagery media. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/divider-vertical-desc.png + :align: center + +.. code-block:: kv + + MDDivider: + size_hint_y: .5 + orientation: "vertical" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/divider-vertical.png + :align: center + +API break +========= + +1.2.0 version +------------- + +.. code-block:: kv + + MDSeparator: + [...] + +2.0.0 version +------------- + +.. code-block:: kv + + MDDivider: + [...] +""" + +__all__ = ("MDDivider",) + +import os + +from kivy.clock import Clock +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import ColorProperty, NumericProperty +from kivy.uix.boxlayout import BoxLayout + +from kivymd import uix_path + +with open( + os.path.join(uix_path, "divider", "divider.kv"), encoding="utf-8" +) as kv_file: + Builder.load_string(kv_file.read()) + + +class MDDivider(BoxLayout): + """ + A divider line. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + color = ColorProperty(None) + """ + Divider color in (r, g, b, a) or string format. + + :attr:`color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + divider_width = NumericProperty(dp(1)) + """ + Divider width. + + :attr:`divider_width` is an :class:`~kivy.properties.NumericProperty` + and defaults to `dp(1)`. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + Clock.schedule_once(self.on_orientation) + + def on_orientation(self, *args) -> None: + """Fired when the values of :attr:`orientation` change.""" + + if self.orientation == "vertical": + self.size_hint_x = None + self.width = self.divider_width + elif self.orientation == "horizontal": + self.size_hint_y = None + self.height = self.divider_width diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__init__.py index a2ca639..d3574d7 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__init__.py @@ -1 +1 @@ -from .dropdownitem import MDDropDownItem # NOQA F401 +from .dropdownitem import MDDropDownItem, MDDropDownItemText # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/__init__.cpython-311.pyc index 4c3a9a7..a3ac579 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/dropdownitem.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/dropdownitem.cpython-311.pyc index 34492c2..c6614e7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/dropdownitem.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/__pycache__/dropdownitem.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.kv index 82960e3..431a7b3 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.kv @@ -1,38 +1,43 @@ -<_Triangle>: + + size_hint: None, None + size: + (self._size[0] + dp(12), self._size[1] + dp(8)) \ + if self._size else \ + (0, 0) + md_bg_color: "red" + canvas: Color: - rgba: app.theme_cls.text_color + group: "drop-down-item-color" + Rectangle: + group: "drop-down-item-text" + texture: + self._drop_down_text.texture \ + if self._drop_down_text else \ + None + size: + self._drop_down_text.texture_size \ + if self._drop_down_text else \ + (0, 0) + pos: + self.x, self.y + dp(8) + Color: + group: "drop-down-item-triangle-color" Triangle: points: [ \ - self.right-dp(14), self.y+dp(7), \ - self.right-dp(7), self.y+dp(7), \ - self.right-dp(7), self.y+dp(14) \ + self.right + dp(0), self.y + dp(12), \ + self.right - dp(6), self.y + dp(12), \ + self.right - dp(6), self.y + root.height - dp(3) \ ] - - - orientation: "vertical" - size_hint: None, None - size: self.minimum_size - spacing: "5dp" - padding: "5dp", "5dp", "5dp", 0 - - MDBoxLayout: - adaptive_size: True - spacing: "10dp" - - Label: - id: label_item - size_hint: None, None - size: self.texture_size - color: root.theme_cls.text_color - disabled_color: root.theme_cls.disabled_hint_text_color - font_size: root.font_size + MDDivider: + size_hint_x: None + width: root._size[0] + dp(14) if root._size else 0 - _Triangle: - size_hint: None, None - size: "20dp", "20dp" - - MDSeparator: + + size_hint_x: None + width: self.texture_size[0] + adaptive_width: True + role: "small" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.py index 951357d..24bdb1b 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/dropdownitem/dropdownitem.py @@ -11,46 +11,83 @@ Usage .. code-block:: python from kivy.lang import Builder + from kivymd.uix.menu import MDDropdownMenu from kivymd.app import MDApp KV = ''' MDScreen + md_bg_color: self.theme_cls.backgroundColor MDDropDownItem: - id: drop_item - pos_hint: {'center_x': .5, 'center_y': .5} - text: 'Item' - on_release: print("Press item") + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.open_menu(self) + + MDDropDownItemText: + id: drop_text + text: "Item" ''' - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) + class Example(MDApp): + def open_menu(self, item): + menu_items = [ + { + "text": f"{i}", + "on_release": lambda x=f"Item {i}": self.menu_callback(x), + } for i in range(5) + ] + MDDropdownMenu(caller=item, items=menu_items).open() + + def menu_callback(self, text_item): + self.root.ids.drop_text.text = text_item def build(self): - return self.screen + return Builder.load_string(KV) - Test().run() + Example().run() .. seealso:: `Work with the class MDDropdownMenu see here `_ + +API break +========= + +1.2.0 version +------------- + +.. code-block:: kv + + MDDropDownItem: + text: 'Item' + on_release: print(*args) + +2.0.0 version +------------- + +.. code-block:: kv + + MDDropDownItem: + on_release: print(*args) + + MDDropDownItemText: + text: "Item text" """ -__all__ = ("MDDropDownItem",) +__all__ = ("MDDropDownItem", "MDDropDownItemText") import os +from kivy.clock import Clock from kivy.lang import Builder -from kivy.properties import NumericProperty, StringProperty +from kivy.metrics import dp +from kivy.properties import ObjectProperty, ListProperty from kivy.uix.behaviors import ButtonBehavior from kivy.uix.boxlayout import BoxLayout -from kivy.uix.widget import Widget +from kivymd.uix.label import MDLabel from kivymd import uix_path from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import DeclarativeBehavior @@ -61,8 +98,19 @@ with open( Builder.load_string(kv_file.read()) -class _Triangle(Widget): - pass +# FIXME: When resizing the texture of the `MDDropDownItemText` widget, +# the canvas instruction that implements the triangle looks terrible. +# You need to edit the Triangle instructions according to the size +# of the `MDDropDownItemText` texture. +class MDDropDownItemText(MDLabel): + """ + Base texture for :class:`~MDDropDownItem` class (item text). + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + + .. versionadded:: 2.0.0 + """ class MDDropDownItem( @@ -72,42 +120,52 @@ class MDDropDownItem( Dropdown item class. For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and :class:`~kivymd.theming.ThemableBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation. """ - text = StringProperty() - """ - Text item. + _drop_down_text = ObjectProperty() + _size = ListProperty() - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDDropDownItemText): + self._drop_down_text = widget + widget.bind(text=self.update_text_item) + Clock.schedule_once(lambda x: self.on_disabled(self, self.disabled)) + else: + return super().add_widget(widget) - current_item = StringProperty() - """ - Current name item. + def update_text_item(self, instance, value) -> None: + """Updates the text of the item.""" - :attr:`current_item` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ + self._drop_down_text.texture_update() + drop_down_item_text = self.canvas.get_group("drop-down-item-text")[0] + drop_down_item_text.texture = None + drop_down_item_text.texture = self._drop_down_text.texture + drop_down_item_text.size = self._drop_down_text.texture_size + drop_down_item_text.pos = (self.x, self.y + dp(8)) + self._size = self._drop_down_text.texture_size - font_size = NumericProperty("16sp") - """ - Item font size. + def on_disabled(self, instance, value) -> None: + """Fired when the values of :attr:`disabled` change.""" - :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` - and defaults to `'16sp'`. - """ + self._drop_down_text.disabled = value + drop_down_item_triangle_color = self.canvas.get_group( + "drop-down-item-triangle-color" + )[0] + drop_down_item_triangle_color.rgba = ( + self._drop_down_text.disabled_color + if value + else self._drop_down_text.color + ) - def on_text(self, instance_drop_down_item, text_item: str) -> None: - self.ids.label_item.text = text_item + def on__drop_down_text(self, instance, value) -> None: + """Fired when the values of :attr:`_drop_down_text` change.""" - def set_item(self, name_item: str) -> None: - """Sets new text for an item.""" + def set_size(*args): + self._size = value.texture_size - self.ids.label_item.text = name_item - self.current_item = name_item + Clock.schedule_once(set_size) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__init__.py index 2f5badd..dbe183c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__init__.py @@ -1,8 +1,6 @@ # NOQA F401 from .expansionpanel import ( MDExpansionPanel, - MDExpansionPanelLabel, - MDExpansionPanelOneLine, - MDExpansionPanelThreeLine, - MDExpansionPanelTwoLine, + MDExpansionPanelContent, + MDExpansionPanelHeader, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/__init__.cpython-311.pyc index 31ab28e..b5a4670 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/expansionpanel.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/expansionpanel.cpython-311.pyc index 0660361..0b0f3dd 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/expansionpanel.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/__pycache__/expansionpanel.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.kv index 4aadaef..0ecc4e2 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.kv @@ -1,18 +1,15 @@ -: - icon: "chevron-right" - disabled: True - md_bg_color_disabled: 0, 0, 0, 0 - - canvas.before: - PushMatrix - Rotate: - angle: self._angle - axis: (0, 0, 1) - origin: self.center - canvas.after: - PopMatrix - - + orientation: "vertical" size_hint_y: None - # height: dp(68) + height: self.minimum_height + + + + size_hint_y: None + height: self.minimum_height + + + + size_hint_y: None + height: self.minimum_height + opacity: 0 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.py index d0fe33d..670e701 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/expansionpanel/expansionpanel.py @@ -2,10 +2,6 @@ Components/ExpansionPanel ========================= -.. seealso:: - - `Material Design spec, Expansion panel `_ - .. rubric:: Expansion panels contain creation flows and allow lightweight editing of an element. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.png @@ -16,160 +12,311 @@ Usage .. code-block:: python - self.add_widget( - MDExpansionPanel( - icon="logo.png", # panel icon - content=Content(), # panel content - panel_cls=MDExpansionPanelOneLine(text="Secondary text"), # panel class - ) - ) + MDExpansionPanel: -To use :class:`~MDExpansionPanel` you must pass one of the following classes -to the :attr:`~MDExpansionPanel.panel_cls` parameter: + MDExpansionPanelHeader: -- :class:`~MDExpansionPanelOneLine` -- :class:`~MDExpansionPanelTwoLine` -- :class:`~MDExpansionPanelThreeLine` + # Content of header. + [...] -These classes are inherited from the following classes: + MDExpansionPanelContent: -- :class:`~kivymd.uix.list.OneLineAvatarIconListItem` -- :class:`~kivymd.uix.list.TwoLineAvatarIconListItem` -- :class:`~kivymd.uix.list.ThreeLineAvatarIconListItem` + # Content of panel. + [...] -.. code-block:: python +Anatomy +------- - self.root.ids.box.add_widget( - MDExpansionPanel( - icon="logo.png", - content=Content(), - panel_cls=MDExpansionPanelThreeLine( - text="Text", - secondary_text="Secondary text", - tertiary_text="Tertiary text", - ) - ) - ) +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel-anatomy.png + :align: center Example ------- .. code-block:: python - import os - from kivy.lang import Builder + from kivy.uix.behaviors import ButtonBehavior from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine - from kivymd import images_path + from kivymd.uix.behaviors import RotateBehavior + from kivymd.uix.expansionpanel import MDExpansionPanel + from kivymd.uix.list import MDListItemTrailingIcon KV = ''' - - adaptive_height: True + MDScreen: + md_bg_color: self.theme_cls.backgroundColor - TwoLineIconListItem: - text: "(050)-123-45-67" - secondary_text: "Mobile" + MDExpansionPanel: + id: panel + pos_hint: {"center_x": .5, "center_y": .5} - IconLeftWidget: - icon: 'phone' + MDExpansionPanelHeader: + MDListItem: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.surfaceContainerLowColor + ripple_effect: False - MDScrollView: + MDListItemSupportingText: + text: "Supporting text" - MDGridLayout: - id: box - cols: 1 - adaptive_height: True + TrailingPressedIconButton: + id: chevron + icon: "chevron-right" + on_release: app.tap_expansion_chevron(panel, chevron) + + MDExpansionPanelContent: + orientation: "vertical" + padding: "12dp", 0, "12dp", 0 + + MDLabel: + text: "Channel information" + adaptive_height: True + padding_x: "16dp" + padding_y: "12dp" + + MDListItem: + + MDListItemLeadingIcon: + icon: "email" + + MDListItemHeadlineText: + text: "Email" + + MDListItemSupportingText: + text: "kivydevelopment@gmail.com" + + MDListItem: + + MDListItemLeadingIcon: + icon: "instagram" + + MDListItemHeadlineText: + text: "Instagram" + + MDListItemSupportingText: + text: "Account" + + MDListItemTertiaryText: + text: "www.instagram.com/KivyMD" ''' - class Content(MDBoxLayout): - '''Custom content.''' + class TrailingPressedIconButton( + ButtonBehavior, RotateBehavior, MDListItemTrailingIcon + ): + ... - class Test(MDApp): + class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" return Builder.load_string(KV) - def on_start(self): - for i in range(10): - self.root.ids.box.add_widget( - MDExpansionPanel( - icon=os.path.join(images_path, "logo", "kivymd-icon-128.png"), - content=Content(), - panel_cls=MDExpansionPanelThreeLine( - text="Text", - secondary_text="Secondary text", - tertiary_text="Tertiary text", - ) - ) - ) + def tap_expansion_chevron( + self, panel: MDExpansionPanel, chevron: TrailingPressedIconButton + ): + panel.open() if not panel.is_open else panel.close() + panel.set_chevron_down( + chevron + ) if not panel.is_open else panel.set_chevron_up(chevron) - Test().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel-example.gif :align: center -Two events are available for :class:`~MDExpansionPanel` -------------------------------------------------------- - -- :attr:`~MDExpansionPanel.on_open` -- :attr:`~MDExpansionPanel.on_close` - -.. code-block:: kv - - MDExpansionPanel: - on_open: app.on_panel_open(args) - on_close: app.on_panel_close(args) - -The user function takes one argument - the object of the panel: +Use with ScrollView +------------------- .. code-block:: python - def on_panel_open(self, instance_panel): - print(instance_panel) + import asynckivy + from kivy.animation import Animation + from kivy.lang import Builder + from kivy.metrics import dp + from kivy.uix.behaviors import ButtonBehavior -.. seealso:: `See Expansion panel example `_ + from kivymd.app import MDApp + from kivymd.uix.behaviors import RotateBehavior + from kivymd.uix.expansionpanel import MDExpansionPanel + from kivymd.uix.list import MDListItemTrailingIcon - `Expansion panel and MDCard `_ + KV = ''' + + + MDExpansionPanelHeader: + + MDListItem: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.surfaceContainerLowColor + ripple_effect: False + + MDListItemSupportingText: + text: "Supporting text" + + TrailingPressedIconButton: + id: chevron + icon: "chevron-right" + on_release: app.tap_expansion_chevron(root, chevron) + + MDExpansionPanelContent: + orientation: "vertical" + padding: "12dp", 0, "12dp", "12dp" + md_bg_color: self.theme_cls.surfaceContainerLowestColor + + MDLabel: + text: "Channel information" + adaptive_height: True + padding_x: "16dp" + padding_y: "12dp" + + MDListItem: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.surfaceContainerLowestColor + + MDListItemLeadingIcon: + icon: "email" + + MDListItemHeadlineText: + text: "Email" + + MDListItemSupportingText: + text: "kivydevelopment@gmail.com" + + MDListItem: + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.surfaceContainerLowestColor + + MDListItemLeadingIcon: + icon: "instagram" + + MDListItemHeadlineText: + text: "Instagram" + + MDListItemSupportingText: + text: "Account" + + MDListItemTertiaryText: + text: "www.instagram.com/KivyMD" + + + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + ScrollView: + size_hint_x: .5 + pos_hint: {"center_x": .5, "center_y": .5} + + MDList: + id: container + ''' + + + class ExpansionPanelItem(MDExpansionPanel): + ... + + + class TrailingPressedIconButton( + ButtonBehavior, RotateBehavior, MDListItemTrailingIcon + ): + ... + + + class Example(MDApp): + def on_start(self): + async def set_panel_list(): + for i in range(12): + await asynckivy.sleep(0) + self.root.ids.container.add_widget(ExpansionPanelItem()) + + asynckivy.start(set_panel_list()) + + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + def tap_expansion_chevron( + self, panel: MDExpansionPanel, chevron: TrailingPressedIconButton + ): + Animation( + padding=[0, dp(12), 0, dp(12)] + if not panel.is_open + else [0, 0, 0, 0], + d=0.2, + ).start(panel) + panel.open() if not panel.is_open else panel.close() + panel.set_chevron_down( + chevron + ) if not panel.is_open else panel.set_chevron_up(chevron) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel-example-with-scroll-view.gif + :align: center + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + MDExpansionPanel( + icon="icon.png", + content=Content(), # content of panel + panel_cls=MDExpansionPanelThreeLine( # content of header + text="Text", + secondary_text="Secondary text", + tertiary_text="Tertiary text", + ) + ) + +2.0.0 version +------------- + +.. code-block:: python + + MDExpansionPanel: + + MDExpansionPanelHeader: + + # Content of header. + [...] + + MDExpansionPanelContent: + + # Content of panel. + [...] """ __all__ = ( "MDExpansionPanel", - "MDExpansionPanelOneLine", - "MDExpansionPanelTwoLine", - "MDExpansionPanelThreeLine", - "MDExpansionPanelLabel", + "MDExpansionPanelContent", + "MDExpansionPanelHeader", ) import os -from typing import Union from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder from kivy.metrics import dp -from kivy.properties import NumericProperty, ObjectProperty, StringProperty -from kivy.uix.relativelayout import RelativeLayout -from kivy.uix.widget import WidgetException - -import kivymd.material_resources as m_res -from kivymd import uix_path -from kivymd.icon_definitions import md_icons -from kivymd.uix.button import MDIconButton -from kivymd.uix.list import ( - IconLeftWidget, - ImageLeftWidget, - IRightBodyTouch, - OneLineAvatarIconListItem, - ThreeLineAvatarIconListItem, - TwoLineAvatarIconListItem, - TwoLineListItem, +from kivy.properties import ( + NumericProperty, + ObjectProperty, + StringProperty, + BooleanProperty, ) +from kivy.uix.boxlayout import BoxLayout + +from kivymd import uix_path +from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import BackgroundColorBehavior, DeclarativeBehavior with open( os.path.join(uix_path, "expansionpanel", "expansionpanel.kv"), @@ -178,92 +325,53 @@ with open( Builder.load_string(kv_file.read()) -class MDExpansionChevronRight(IRightBodyTouch, MDIconButton): - """Chevron icon on the right panel.""" - - _angle = NumericProperty(0) - - -class MDExpansionPanelOneLine(OneLineAvatarIconListItem): +class MDExpansionPanelContent( + DeclarativeBehavior, ThemableBehavior, BackgroundColorBehavior, BoxLayout +): """ - Single line panel. + Implements a container for panel content. + + .. versionadded:: 2.0.0 For more information, see in the - :class:`~kivymd.uix.list.OneLineAvatarIconListItem` class documentation. + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. """ -class MDExpansionPanelTwoLine(TwoLineAvatarIconListItem): +class MDExpansionPanelHeader(DeclarativeBehavior, BoxLayout): """ - Two-line panel. + Implements a container for the content of the panel header. + + .. versionadded:: 2.0.0 For more information, see in the - :class:`~kivymd.uix.list.TwoLineAvatarIconListItem` class documentation. + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. """ -class MDExpansionPanelThreeLine(ThreeLineAvatarIconListItem): - """ - Three-line panel. - - For more information, see in the - :class:`~kivymd.uix.list.ThreeLineAvatarIconListItem` class documentation. - """ - - -class MDExpansionPanelLabel(TwoLineListItem): - """ - Label panel. - - For more information, see in the - :class:`~kivymd.uix.list.TwoLineListItem` class documentation. - - ..warning:: This class is created for use in the - :class:`~kivymd.uix.stepper.MDStepperVertical` and - :class:`~kivymd.uix.stepper.MDStepper` classes, and has not - been tested for use outside of these classes. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_paddings) - - def set_paddings(self, interval: Union[int, float]) -> None: - self._txt_bot_pad = dp(36) - self._txt_left_pad = dp(0) - - -class MDExpansionPanel(RelativeLayout): +# TODO: Add a successor from kivymd.uix.behaviors.motion_behavior.MotionBase +# to the MDExpansionPanel class to control the properties of the panel +# opening/closing animation. +class MDExpansionPanel(DeclarativeBehavior, BoxLayout): """ Expansion panel class. For more information, see in the - :class:`~kivy.uix.relativelayout.RelativeLayout` classes documentation. + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. :Events: :attr:`on_open` - Called when a panel is opened. + Fired when a panel is opened. :attr:`on_close` - Called when a panel is closed. - """ - - content = ObjectProperty() - """ - Content of panel. Must be `Kivy` widget. - - :attr:`content` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - icon = StringProperty() - """ - Icon of panel. - - Icon Should be either be a path to an image or - a logo name in :class:`~kivymd.icon_definitions.md_icons` - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. + Fired when a panel is closed. """ opening_transition = StringProperty("out_cubic") @@ -300,187 +408,122 @@ class MDExpansionPanel(RelativeLayout): and defaults to `0.2`. """ - panel_cls = ObjectProperty() + is_open = BooleanProperty(False) """ - Panel object. The object must be one of the classes - :class:`~MDExpansionPanelOneLine`, :class:`~MDExpansionPanelTwoLine` or - :class:`~MDExpansionPanelThreeLine`. + The panel is open or closed. - :attr:`panel_cls` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. + .. versionadded:: 2.0.0 + + :attr:`is_open` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. """ - _state = StringProperty("close") - _anim_playing = False + _header = ObjectProperty() # MDExpansionPanelHeader object + _content = ObjectProperty() # MDExpansionPanelContent object + # Height of the MDExpansionPanelContent widget. + _original_content_height = NumericProperty() + _allow_add_content = False + _panel_is_process_opening = False def __init__(self, **kwargs): super().__init__(**kwargs) self.register_event_type("on_open") self.register_event_type("on_close") - if self.panel_cls and isinstance( - self.panel_cls, - ( - MDExpansionPanelOneLine, - MDExpansionPanelTwoLine, - MDExpansionPanelThreeLine, - MDExpansionPanelLabel, - ), - ): - self.panel_cls.pos_hint = {"top": 1} - self.panel_cls._no_ripple_effect = True - self.panel_cls.bind( - on_release=lambda x: self.check_open_panel(self.panel_cls) - ) - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - self.chevron = MDExpansionChevronRight() - self.panel_cls.add_widget(self.chevron) - if self.icon: - if self.icon in md_icons.keys(): - self.panel_cls.add_widget( - IconLeftWidget( - icon=self.icon, - pos_hint={"center_y": 0.5}, - ) - ) - else: - self.panel_cls.add_widget( - ImageLeftWidget( - source=self.icon, pos_hint={"center_y": 0.5} - ) - ) - else: - self.panel_cls.remove_widget( - self.panel_cls.ids._left_container - ) - self.panel_cls._txt_left_pad = 0 - else: - # if no icon - self.panel_cls._txt_left_pad = m_res.HORIZ_MARGINS - self.add_widget(self.panel_cls) - else: - raise ValueError( - "KivyMD: `panel_cls` object must be must be one of the " - "objects from the list\n" - "[MDExpansionPanelOneLine, MDExpansionPanelTwoLine, " - "MDExpansionPanelThreeLine]" - ) + def on_open(self, *args) -> None: + """Fired when a panel is opened.""" - def on_open(self, *args): - """Called when a panel is opened.""" + def on_close(self, *args) -> None: + """Fired when a panel is closed.""" - def on_close(self, *args): - """Called when a panel is closed.""" - - def check_open_panel( - self, - instance_panel: [ - MDExpansionPanelThreeLine, - MDExpansionPanelTwoLine, - MDExpansionPanelThreeLine, - MDExpansionPanelLabel, - ], - ) -> None: - """ - Called when you click on the panel. Called methods to open or close - a panel. - """ - - press_current_panel = False - for panel in self.parent.children: - if isinstance(panel, MDExpansionPanel): - if len(panel.children) == 2: - if instance_panel is panel.children[1]: - press_current_panel = True - panel.remove_widget(panel.children[0]) - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - chevron = panel.children[0].children[0].children[0] - self.set_chevron_up(chevron) - self.close_panel(panel, press_current_panel) - self.dispatch("on_close") - break - if not press_current_panel: - self.set_chevron_down() - - def set_chevron_down(self) -> None: + def set_chevron_down(self, instance) -> None: """Sets the chevron down.""" - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - Animation(_angle=-90, d=self.opening_time).start(self.chevron) - self.open_panel() - self.dispatch("on_open") + Animation(rotate_value_angle=-90, d=self.opening_time).start(instance) - def set_chevron_up(self, instance_chevron: MDExpansionChevronRight) -> None: + def set_chevron_up(self, instance) -> None: """Sets the chevron up.""" - if not isinstance(self.panel_cls, MDExpansionPanelLabel): - Animation(_angle=0, d=self.closing_time).start(instance_chevron) + Animation(rotate_value_angle=0, d=self.closing_time).start(instance) - def close_panel( - self, instance_expansion_panel, press_current_panel: bool - ) -> None: - """Method closes the panel.""" + def close(self, *args) -> None: + """ + Method closes the panel. - if self._anim_playing: - return + .. versionchanged:: 2.0.0 - if press_current_panel: - self._anim_playing = True + Rename from `close_panel` to `close` method. + """ - self._state = "close" + def set_content_height(*args): + anim_height = Animation( + height=0, + t=self.opening_transition, + d=self.opening_time, + ) + anim_height.bind( + on_complete=lambda *args: self.remove_widget(self._content) + ) + anim_height.start(self._content) + self.is_open = False + self.dispatch("on_close") - anim = Animation( - height=self.panel_cls.height, - d=self.closing_time, - t=self.closing_transition, - ) - anim.bind(on_complete=self._disable_anim) - anim.start(instance_expansion_panel) - - def open_panel(self, *args) -> None: - """Method opens a panel.""" - - if self._anim_playing: - return - - self._anim_playing = True - self._state = "open" - - anim = Animation( - height=self.content.height + self.height, - d=self.opening_time, + anim_opacity = Animation( + opacity=0, t=self.opening_transition, + d=self.opening_time, ) - anim.bind(on_complete=self._add_content) - anim.bind(on_complete=self._disable_anim) - anim.start(self) + anim_opacity.bind(on_complete=set_content_height) + anim_opacity.start(self._content) - def get_state(self) -> str: - """Returns the state of panel. Can be `close` or `open` .""" + def open(self, *args) -> None: + """ + Method opens a panel. - return self._state + .. versionchanged:: 2.0.0 + + Rename from `open_panel` to `open` method. + """ + + def set_content_opacity(*args): + Animation( + opacity=1, + t=self.opening_transition, + d=self.opening_time, + ).start(self._content) + self.is_open = True + self._panel_is_process_opening = False + self.dispatch("on_open") + + if not self._panel_is_process_opening: + self._allow_add_content = True + self._panel_is_process_opening = True + self.add_widget(self._content) + + anim_height = Animation( + height=self._original_content_height, + t=self.opening_transition, + d=self.opening_time, + ) + anim_height.bind(on_complete=set_content_opacity) + anim_height.start(self._content) def add_widget(self, widget, index=0, canvas=None): - if isinstance( - widget, - ( - MDExpansionPanelOneLine, - MDExpansionPanelTwoLine, - MDExpansionPanelThreeLine, - MDExpansionPanelLabel, - ), + if isinstance(widget, MDExpansionPanelHeader): + self._header = widget + return super().add_widget(widget) + elif ( + isinstance(widget, MDExpansionPanelContent) + and not self._allow_add_content ): - self.height = widget.height - return super().add_widget(widget) + self._content = widget + Clock.schedule_once(self._set_content_height, 0.8) + elif ( + isinstance(widget, MDExpansionPanelContent) + and self._allow_add_content + ): + return super().add_widget(widget) - def _disable_anim(self, *args): - self._anim_playing = False - - def _add_content(self, *args): - if self.content: - try: - if isinstance(self.panel_cls, MDExpansionPanelLabel): - self.content.y = dp(36) - self.add_widget(self.content) - except WidgetException: - pass + def _set_content_height(self, *args): + self._original_content_height = self._content.height - dp(88) + self._content.height = 0 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/__init__.cpython-311.pyc index 35601b5..012024b 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/filemanager.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/filemanager.cpython-311.pyc index bf28292..5d8fc3e 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/filemanager.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/__pycache__/filemanager.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.kv index 29352d7..ceb6e19 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.kv @@ -1,8 +1,7 @@ #:import os os -#:import FILE_MANAGER_TOP_APP_BAR_ELEVATION kivymd.material_resources.FILE_MANAGER_TOP_APP_BAR_ELEVATION - + icon: "folder" path: "" background_normal: "" @@ -10,34 +9,18 @@ dir_or_file_name: "" icon_color: 0, 0, 0, 0 _selected: False - events_callback: lambda x: None - orientation: "vertical" + on_release: root.events_callback(root.path, root) - ModifiedOneLineIconListItem: + MDListItemLeadingIcon: + icon: root.icon + theme_icon_color: "Custom" + icon_color: root.icon_color + + MDListItemSupportingText: text: root.dir_or_file_name - on_release: root.events_callback(root.path, root) - bg_color: - self.theme_cls.bg_darkest \ - if root._selected else \ - self.theme_cls.bg_normal - - IconLeftWidget: - icon: root.icon - theme_icon_color: "Custom" - icon_color: root.icon_color - - MDSeparator: - - adaptive_height: True - shorten: True - shorten_from: "center" - halign: "center" - text_size: self.width, None - - - + name: "" path: "" realpath: "" @@ -46,48 +29,68 @@ _selected: False orientation: "vertical" size_hint_y: None - hright: root.height - padding: dp(20) + height: root.height + padding: "20dp" + spacing: "12dp" - IconButton: + MDFileManagerThumbnail: mipmap: True source: root.path - bg_color: - app.theme_cls.bg_darkest \ - if root._selected else app.theme_cls.bg_normal on_release: root.events_callback( \ os.path.join(root.path if root.type != "folder" \ else root.realpath, root.name), root) - LabelContent: + MDLabel: text: root.name + adaptive_height: True + shorten: True + shorten_from: "center" + halign: "center" + text_size: self.width, None - md_bg_color: root.theme_cls.bg_normal + canvas: + Color: + rgba: self.theme_cls.backgroundColor + Rectangle: + pos: self.pos + size: self.size - MDBoxLayout: + BoxLayout: orientation: "vertical" spacing: dp(5) MDTopAppBar: id: toolbar - title: root.current_path - right_action_items: [["close-box", lambda x: root.exit_manager(1)]] - left_action_items: [["chevron-left", lambda x: root.back()]] - elevation: FILE_MANAGER_TOP_APP_BAR_ELEVATION md_bg_color: - app.theme_cls.primary_color \ + app.theme_cls.surfaceColor \ if not root.background_color_toolbar else \ root.background_color_toolbar + MDTopAppBarLeadingButtonContainer: + padding: "12dp", 0, 0, 0 + + MDActionTopAppBarButton: + icon: "chevron-left" + on_release: root.back() + + MDTopAppBarTitle: + text: root.current_path + + MDTopAppBarTrailingButtonContainer: + + MDActionTopAppBarButton: + icon: "close-box" + on_release: root.exit_manager(1) + RecycleView: id: rv key_viewclass: "viewclass" key_size: "height" bar_width: dp(4) - bar_color: root.theme_cls.primary_color + bar_color: root.theme_cls.primaryColor RecycleGridLayout: padding: "10dp" @@ -97,13 +100,3 @@ default_size_hint: 1, None size_hint_y: None height: self.minimum_height - - - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.py index 4073764..86f793e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/filemanager/filemanager.py @@ -47,28 +47,22 @@ Example from kivy.core.window import Window from kivy.lang import Builder + from kivy.metrics import dp from kivymd.app import MDApp from kivymd.uix.filemanager import MDFileManager - from kivymd.toast import toast - + from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText KV = ''' - MDBoxLayout: - orientation: "vertical" + MDScreen: + md_bg_color: self.theme_cls.backgroundColor - MDTopAppBar: - title: "MDFileManager" - left_action_items: [["menu", lambda x: None]] - elevation: 3 + MDButton: + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.file_manager_open() - MDFloatLayout: - - MDRoundFlatIconButton: + MDButtonText: text: "Open manager" - icon: "folder" - pos_hint: {"center_x": .5, "center_y": .5} - on_release: app.file_manager_open() ''' @@ -83,11 +77,11 @@ Example def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) def file_manager_open(self): - self.file_manager.show(os.path.expanduser("~")) # output manager to the screen + self.file_manager.show( + os.path.expanduser("~")) # output manager to the screen self.manager_open = True def select_path(self, path: str): @@ -99,7 +93,14 @@ Example ''' self.exit_manager() - toast(path) + MDSnackbar( + MDSnackbarText( + text=path, + ), + y=dp(24), + pos_hint={"center_x": 0.5}, + size_hint_x=0.8, + ).open() def exit_manager(self, *args): '''Called when the user reaches the root of the directory tree.''' @@ -133,6 +134,8 @@ Not tested on `iOS`. :align: center """ +from __future__ import annotations + __all__ = ("MDFileManager",) import locale @@ -142,28 +145,27 @@ from typing import List, Tuple, Union from kivy import platform from kivy.clock import Clock -from kivy.factory import Factory from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( BooleanProperty, ColorProperty, ListProperty, - NumericProperty, ObjectProperty, OptionProperty, StringProperty, ) from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout from kivy.uix.modalview import ModalView +from kivy.uix.relativelayout import RelativeLayout from kivymd import images_path, uix_path +from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import CircularRippleBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDFloatingActionButton +from kivymd.uix.button import MDFabButton from kivymd.uix.fitimage import FitImage -from kivymd.uix.list import BaseListItem -from kivymd.uix.relativelayout import MDRelativeLayout +from kivymd.uix.list import MDListItem with open( os.path.join(uix_path, "filemanager", "filemanager.kv"), encoding="utf-8" @@ -171,37 +173,57 @@ with open( Builder.load_string(kv_file.read()) -class BodyManager(MDBoxLayout): - """Base class for folders and files icons.""" - - -class BodyManagerWithPreview(MDBoxLayout): +class MDFileManagerItem(MDListItem): """ - Base class for folder icons and thumbnails images in ``preview`` mode. + Base class for folders and files icons. + + .. versionchanged:: 2.0.0 + + The `BodyManager` class has been renamed to `MDFileManagerItem`. + + For more information, see in the + :class:`~kivymd.uix.list.list.MDListItem` class documentation. """ -class IconButton(CircularRippleBehavior, ButtonBehavior, FitImage): - """Folder icons/thumbnails images in ``preview`` mode.""" +class MDFileManagerItemPreview(BoxLayout): + """ + Base class for folder icons and thumbnails images in `preview` mode. + + .. versionchanged:: 2.0.0 + + The `BodyManagerWithPreview` class has been renamed + to `MDFileManagerItemPreview`. + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ -class ModifiedOneLineIconListItem(BaseListItem): - _txt_left_pad = NumericProperty("72dp") - _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") - _num_lines = 1 +class MDFileManagerThumbnail(CircularRippleBehavior, ButtonBehavior, FitImage): + """ + Folder icons/thumbnails images in `preview` mode. - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.height = dp(48) + .. versionchanged:: 2.0.0 + + The `IconButton` class has been renamed to `MDFileManagerThumbnail`. + + For more information, see in the + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.fitimage.fitimage.FitImage` + classes documentation. + """ -class MDFileManager(MDRelativeLayout): +class MDFileManager(ThemableBehavior, RelativeLayout): """ Implements a modal dialog with a file manager. For more information, see in the - :class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation. + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.relativelayout.RelativeLayout` + classes documentation. :Events: `on_pre_open`: @@ -400,8 +422,8 @@ class MDFileManager(MDRelativeLayout): ) """ It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files - by option. By default, sort by name. Available options are: - `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`. + by option. By default, sort by name. + Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`. :attr:`sort_by` is an :class:`~kivy.properties.OptionProperty` and defaults to `name`. @@ -452,9 +474,6 @@ class MDFileManager(MDRelativeLayout): self.register_event_type("on_open") self.register_event_type("on_pre_dismiss") self.register_event_type("on_dismiss") - - toolbar_label = self.ids.toolbar.children[1].children[0] - toolbar_label.font_style = "Subtitle1" Clock.schedule_once(self._create_selection_button) if self.preview: @@ -499,7 +518,7 @@ class MDFileManager(MDRelativeLayout): manager_list.append( { - "viewclass": "BodyManager", + "viewclass": "MDFileManagerItem", "path": disk, "icon": icon, "dir_or_file_name": disk, @@ -532,7 +551,7 @@ class MDFileManager(MDRelativeLayout): for name_dir in self.__sort_files(dirs): manager_list.append( { - "viewclass": "BodyManagerWithPreview", + "viewclass": "MDFileManagerItemPreview", "path": self.icon_folder, "realpath": os.path.join(path), "type": "folder", @@ -549,7 +568,7 @@ class MDFileManager(MDRelativeLayout): ): manager_list.append( { - "viewclass": "BodyManagerWithPreview", + "viewclass": "MDFileManagerItemPreview", "path": os.path.join(path, name_file), "name": name_file, "type": "files", @@ -569,12 +588,12 @@ class MDFileManager(MDRelativeLayout): manager_list.append( { - "viewclass": "BodyManager", + "viewclass": "MDFileManagerItem", "path": _path, "icon": icon, "dir_or_file_name": name, "events_callback": self.select_dir_or_file, - "icon_color": self.theme_cls.primary_color + "icon_color": self.theme_cls.primaryColor if not self.icon_color else self.icon_color, "_selected": False, @@ -586,17 +605,18 @@ class MDFileManager(MDRelativeLayout): manager_list.append( { - "viewclass": "BodyManager", + "viewclass": "MDFileManagerItem", "path": name, "icon": "file-outline", "dir_or_file_name": os.path.split(name)[1], "events_callback": self.select_dir_or_file, - "icon_color": self.theme_cls.primary_color + "icon_color": self.theme_cls.primaryColor if not self.icon_color else self.icon_color, "_selected": False, } ) + self.ids.rv.data = manager_list self._show() @@ -663,8 +683,8 @@ class MDFileManager(MDRelativeLayout): def select_dir_or_file( self, path: str, - widget: Union[BodyManagerWithPreview, Factory.BodyManager], - ): + widget: MDFileManagerItemPreview | MDFileManagerItem, + ) -> None: """Called by tap on the name of the directory or file.""" if os.path.isfile(os.path.join(self.current_path, path)): @@ -715,39 +735,39 @@ class MDFileManager(MDRelativeLayout): self.icon_selection_button = icon_name def on_background_color_toolbar( - self, instance_file_manager, color: Union[str, list] + self, instance_file_manager, color: str | list ) -> None: """ Called when the :attr:`background_color_toolbar` property is changed. """ - def on_background_color_toolbar(*args): + def on_background_color_toolbar(*args) -> None: self.ids.toolbar.md_bg_color = color Clock.schedule_once(on_background_color_toolbar) - def on_pre_open(self, *args): + def on_pre_open(self, *args) -> None: """ Default pre-open event handler. .. versionadded:: 1.1.0 """ - def on_open(self, *args): + def on_open(self, *args) -> None: """ Default open event handler. .. versionadded:: 1.1.0 """ - def on_pre_dismiss(self, *args): + def on_pre_dismiss(self, *args) -> None: """ Default pre-dismiss event handler. .. versionadded:: 1.1.0 """ - def on_dismiss(self, *args): + def on_dismiss(self, *args) -> None: """ Default dismiss event handler. @@ -775,15 +795,15 @@ class MDFileManager(MDRelativeLayout): or self.selector == "multi" or self.selector == "folder" ): - self.selection_button = MDFloatingActionButton( + self.selection_button = MDFabButton( on_release=self.select_directory_on_press_button, - md_bg_color=self.theme_cls.primary_color + theme_bg_color="Custom", + md_bg_color=self.theme_cls.primaryColor if not self.background_color_selection_button else self.background_color_selection_button, icon=self.icon_selection_button, pos_hint={"right": 0.99}, y=dp(12), - elevation=0, ) self.add_widget(self.selection_button) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/__init__.cpython-311.pyc index 1fefc72..90447bc 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/fitimage.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/fitimage.cpython-311.pyc index da15de3..049c31f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/fitimage.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/__pycache__/fitimage.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/fitimage.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/fitimage.py index 246bb5a..5079016 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/fitimage.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/fitimage/fitimage.py @@ -2,57 +2,8 @@ Components/FitImage =================== -Feature to automatically crop a `Kivy` image to fit your layout -Write by Benedikt Zwölfer - -Referene - https://gist.github.com/benni12er/95a45eb168fc33a4fcd2d545af692dad - - -Example: -======== - -.. tabs:: - - .. tab:: Declarative KV styles - - .. code-block:: kv - - MDBoxLayout: - size_hint_y: None - height: "200dp" - orientation: 'vertical' - - FitImage: - size_hint_y: 3 - source: 'images/img1.jpg' - - FitImage: - size_hint_y: 1 - source: 'images/img2.jpg' - - .. tab:: Declarative python styles - - .. code-block:: python - - MDBoxLayout( - FitImage( - size_hint_y=.3, - source='images/img1.jpg', - ), - FitImage( - size_hint_y=.7, - source='images/img2.jpg', - ), - size_hint_y=None, - height="200dp", - orientation='vertical', - ) - -Example with round corners: -=========================== - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fitimage-round-corners.png - :align: center +Example +======= .. tabs:: @@ -66,33 +17,36 @@ Example with round corners: KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor - MDCard: - radius: 36 - md_bg_color: "grey" + MDBoxLayout: + radius: "36dp" pos_hint: {"center_x": .5, "center_y": .5} size_hint: .4, .8 + md_bg_color: self.theme_cls.onSurfaceVariantColor FitImage: - source: "bg.jpg" + source: "image.png" size_hint_y: .35 pos_hint: {"top": 1} - radius: 36, 36, 0, 0 + radius: "36dp", "36dp", 0, 0 ''' class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" return Builder.load_string(KV) Example().run() + .. tab:: Declarative python styles .. code-block:: python + from kivy.metrics import dp + from kivymd.app import MDApp from kivymd.uix.card import MDCard from kivymd.uix.fitimage import FitImage @@ -101,19 +55,18 @@ Example with round corners: class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" return ( MDScreen( - MDCard( + MDBoxLayout( FitImage( - source="bg.jpg", + source="image.png", size_hint_y=0.35, pos_hint={"top": 1}, - radius=(36, 36, 0, 0), + radius=(dp(36), dp(36), 0, 0), ), - radius=36, - md_bg_color="grey", - pos_hint={"center_x": .5, "center_y": .5}, + radius=dp(36), + md_bg_color=self.theme_cls.onSurfaceVariantColor, + pos_hint={"center_x": 0.5, "center_y": 0.5}, size_hint=(0.4, 0.8), ), ) @@ -121,118 +74,39 @@ Example with round corners: Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/fitimage-round-corners.png + :align: center """ __all__ = ("FitImage",) -from kivy.clock import Clock -from kivy.graphics.context_instructions import Color -from kivy.graphics.vertex_instructions import Rectangle -from kivy.properties import BooleanProperty, ObjectProperty +from kivy.properties import OptionProperty from kivy.uix.image import AsyncImage -from kivy.uix.widget import Widget from kivymd.uix.behaviors import StencilBehavior -from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.uix.behaviors import DeclarativeBehavior -class FitImage(MDBoxLayout, StencilBehavior): +class FitImage(DeclarativeBehavior, StencilBehavior, AsyncImage): """ Fit image class. For more information, see in the - :class:`~kivymd.uix.boxlayout.MDLayout` and - :class:`~kivymd.uix.behaviors.StencilBehavior` classes documentation. + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.image.AsyncImage` and + :class:`~kivymd.uix.behaviors.stencil_behavior.StencilBehavior` + classes documentation. """ - source = ObjectProperty() + fit_mode = OptionProperty( + "cover", options=["scale-down", "fill", "contain", "cover"] + ) """ - Filename/source of your image. + Image will be stretched horizontally or vertically to fill the widget box, + **maintaining its aspect ratio**. If the image has a different aspect ratio + than the widget, then the image will be clipped to fit. - :attr:`source` is a :class:`~kivy.properties.StringProperty` - and defaults to None. + :attr:`fit_mode` is a :class:`~kivy.properties.OptionProperty` and + defaults to `'cover'`. """ - - mipmap = BooleanProperty(False) - """ - Indicate if you want OpenGL mipmapping to be applied to the texture. - Read :ref:`mipmap` for more information. - - .. versionadded:: 1.0.0 - - :attr:`mipmap` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - _container = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self._late_init) - - def _late_init(self, *args): - self._container = Container(self.source, self.mipmap) - self.bind(source=self._container.setter("source")) - self.add_widget(self._container) - - def reload(self): - self._container.image.reload() - - -class Container(Widget): - source = ObjectProperty() - image = ObjectProperty() - - def __init__(self, source, mipmap, **kwargs): - super().__init__(**kwargs) - self.image = AsyncImage(mipmap=mipmap) - self.loader_clock = Clock.schedule_interval( - self.adjust_size, self.image.anim_delay - ) - self.image.bind( - on_load=lambda inst: ( - self.adjust_size(), - self.loader_clock.cancel(), - ) - ) - self.source = source - self.bind(size=self.adjust_size, pos=self.adjust_size) - - def on_source(self, instance, value): - if isinstance(value, str): - self.image.source = value - else: - self.image.texture = value - self.adjust_size() - - def adjust_size(self, *args): - if not self.parent or not self.image.texture: - return - - (par_x, par_y) = self.parent.size - - if par_x == 0 or par_y == 0: - with self.canvas: - self.canvas.clear() - return - - par_scale = par_x / par_y - (img_x, img_y) = self.image.texture.size - img_scale = img_x / img_y - - if par_scale > img_scale: - (img_x_new, img_y_new) = (img_x, img_x / par_scale) - else: - (img_x_new, img_y_new) = (img_y * par_scale, img_y) - - crop_pos_x = (img_x - img_x_new) / 2 - crop_pos_y = (img_y - img_y_new) / 2 - - subtexture = self.image.texture.get_region( - crop_pos_x, crop_pos_y, img_x_new, img_y_new - ) - - with self.canvas: - self.canvas.clear() - Color(1, 1, 1) - Rectangle(texture=subtexture, pos=self.pos, size=(par_x, par_y)) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/floatlayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/floatlayout.py index b64ddcf..7832d34 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/floatlayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/floatlayout.py @@ -13,7 +13,7 @@ FloatLayout FloatLayout: canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor RoundedRectangle: pos: self.pos size: self.size @@ -26,7 +26,7 @@ MDFloatLayout MDFloatLayout: radius: [25, 0, 0, 0] - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor .. Warning:: For a :class:`~kivy.uix.floatlayout.FloatLayout`, the ``minimum_size`` attributes are always 0, so you cannot use @@ -37,13 +37,24 @@ from kivy.uix.floatlayout import FloatLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDFloatLayout( - DeclarativeBehavior, ThemableBehavior, FloatLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + FloatLayout, + MDAdaptiveWidget, ): """ - Float layout class. For more information, see in the - :class:`~kivy.uix.floatlayout.FloatLayout` class documentation. + Float layout class. + + For more information see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.floatlayout.FloatLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/gridlayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/gridlayout.py index 96bfd79..90f66ec 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/gridlayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/gridlayout.py @@ -16,7 +16,7 @@ GridLayout canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -28,7 +28,7 @@ MDGridLayout MDGridLayout: adaptive_height: True - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor Available options are: ---------------------- @@ -38,6 +38,7 @@ Available options are: - adaptive_size_ .. adaptive_height: + adaptive_height --------------- @@ -53,6 +54,7 @@ Equivalent height: self.minimum_height .. adaptive_width: + adaptive_width -------------- @@ -68,6 +70,7 @@ Equivalent width: self.minimum_width .. adaptive_size: + adaptive_size ------------- @@ -87,13 +90,24 @@ from kivy.uix.gridlayout import GridLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDGridLayout( - DeclarativeBehavior, ThemableBehavior, GridLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + GridLayout, + MDAdaptiveWidget, ): """ - Grid layout class. For more information, see in the - :class:`~kivy.uix.gridlayout.GridLayout` class documentation. + Grid layout class. + + For more information see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.gridlayout.GridLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/hero.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/hero.py index 70bb572..dba8931 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/hero.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/hero.py @@ -30,12 +30,13 @@ following: - On screen **A**, place the :class:`~MDHeroFrom` container. - Sets a tag (string) for the :class:`~MDHeroFrom` container. - Place a hero in the :class:`~MDHeroFrom` container. -- On screen **B**, place the :class:`~MDHeroTo` container - our hero from screen **A **will fly into this container. +- On screen **B**, place the :class:`~MDHeroTo` container - our hero from screen **A** will fly into this container. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-base.png :align: center .. warning:: + :class:`~MDHeroFrom` container cannot have more than one child widget. Base example @@ -63,18 +64,20 @@ Base example x: 24 FitImage: - source: "kivymd/images/logo/kivymd-icon-512.png" + source: "bg.jpg" size_hint: None, None size: hero_from.size - MDRaisedButton: - text: "Move Hero To Screen B" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["hero"] root.current = "screen B" + MDButtonText: + text: "Move Hero To Screen B" + MDScreen: name: "screen B" hero_to: hero_to @@ -87,22 +90,24 @@ Base example size: "220dp", "220dp" pos_hint: {"center_x": .5, "center_y": .5} - MDRaisedButton: - text: "Move Hero To Screen A" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["hero"] root.current = "screen A" + + MDButtonText: + text: "Move Hero To Screen A" ''' - class Test(MDApp): + class Example(MDApp): def build(self): return Builder.load_string(KV) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage.gif :align: center @@ -126,23 +131,27 @@ container in which the hero is located: .. code-block:: kv - MDRaisedButton: - text: "Move Hero To Screen B" + MDButton: on_release: root.current_heroes = ["hero"] root.current = "screen 2" + MDButtonText: + text: "Move Hero To Screen B" + If you need to switch to a screen that does not contain heroes, set the `current_hero` attribute for the screen manager as "" (empty string): .. code-block:: kv - MDRaisedButton: - text: "Go To Another Screen" + MDButton: on_release: root.current_heroes = [] root.current = "another screen" + MDButtonText: + text: "Go To Another Screen" + Example ------- @@ -168,18 +177,20 @@ Example x: 24 FitImage: - source: "kivymd/images/logo/kivymd-icon-512.png" + source: "bg.jpg" size_hint: None, None size: hero_from.size - MDRaisedButton: - text: "Move Hero To Screen B" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["hero"] root.current = "screen B" + MDButtonText: + text: "Move Hero To Screen B" + MDScreen: name: "screen B" hero_to: hero_to @@ -192,44 +203,51 @@ Example size: "220dp", "220dp" pos_hint: {"center_x": .5, "center_y": .5} - MDRaisedButton: - text: "Go To Screen C" + MDButton: pos_hint: {"center_x": .5} y: "52dp" on_release: root.current_heroes = [] root.current = "screen C" - MDRaisedButton: - text: "Move Hero To Screen A" + MDButtonText: + text: "Go To Screen C" + + MDButton: pos_hint: {"center_x": .5} y: "8dp" on_release: root.current_heroes = ["hero"] root.current = "screen A" + MDButtonText: + text: "Move Hero To Screen A" + MDScreen: name: "screen C" + md_bg_color: "olive" MDLabel: text: "Screen C" halign: "center" - MDRaisedButton: - text: "Back To Screen B" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current = "screen B" + + MDButtonText: + text: "Back To Screen B" ''' - class Test(MDApp): + class Example(MDApp): def build(self): return Builder.load_string(KV) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-switch-another-screen.gif :align: center @@ -281,14 +299,16 @@ background color of the hero during the flight between the screens: FitImage: source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png" - MDRaisedButton: - text: "Move Hero To Screen B" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["hero"] root.current = "screen B" + MDButtonText: + text: "Move Hero To Screen B" + MDScreen: name: "screen B" hero_to: hero_to @@ -301,17 +321,19 @@ background color of the hero during the flight between the screens: size: "220dp", "220dp" pos_hint: {"center_x": .5, "center_y": .5} - MDRaisedButton: - text: "Move Hero To Screen A" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["hero"] root.current = "screen A" + + MDButtonText: + text: "Move Hero To Screen A" ''' - class Test(MDApp): + class Example(MDApp): def build(self): return Builder.load_string(KV) @@ -321,7 +343,7 @@ background color of the hero during the flight between the screens: self, instance_hero_widget: MDRelativeLayout, duration: float ): ''' - Called when the hero flies from screen **A** to screen **B**. + Fired when the hero flies from screen **A** to screen **B**. :param instance_hero_widget: dhild widget of the `MDHeroFrom` class. :param duration of the transition animation between screens. @@ -336,7 +358,7 @@ background color of the hero during the flight between the screens: def on_transform_out( self, instance_hero_widget: MDRelativeLayout, duration: float ): - '''Called when the hero back from screen **B** to screen **A**.''' + '''Fired when the hero back from screen **B** to screen **A**.''' Animation( radius=[24, 12, 24, 12], @@ -345,7 +367,7 @@ background color of the hero during the flight between the screens: ).start(instance_hero_widget) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-events.gif :align: center @@ -358,6 +380,7 @@ Usage with ScrollView from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder + from kivy.metrics import dp from kivy.properties import StringProperty, ObjectProperty from kivymd.app import MDApp @@ -367,27 +390,36 @@ Usage with ScrollView size_hint_y: None height: "200dp" - radius: 24 + radius: "24dp" MDSmartTile: id: tile - radius: 24 - box_radius: 0, 0, 24, 24 - box_color: 0, 0, 0, .5 - source: "kivymd/images/logo/kivymd-icon-512.png" size_hint: None, None size: root.size - mipmap: True on_release: root.on_release() - MDLabel: - text: root.tag - bold: True - font_style: "H6" - opposite_colors: True + MDSmartTileImage: + id: image + source: "bg.jpg" + radius: dp(24) + + MDSmartTileOverlayContainer: + id: overlay + md_bg_color: 0, 0, 0, .5 + adaptive_height: True + padding: "8dp" + spacing: "8dp" + radius: [0, 0, dp(24), dp(24)] + + MDLabel: + text: root.tag + theme_text_color: "Custom" + text_color: "white" + adaptive_height: True MDScreenManager: + md_bg_color: self.theme_cls.backgroundColor MDScreen: name: "screen A" @@ -411,13 +443,15 @@ Usage with ScrollView height: "220dp" pos_hint: {"top": 1} - MDRaisedButton: - text: "Move Hero To Screen A" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = [hero_to.tag] root.current = "screen A" + + MDButtonText: + text: "Move Hero To Screen A" ''' @@ -427,21 +461,26 @@ Usage with ScrollView def __init__(self, **kwargs): super().__init__(**kwargs) - self.ids.tile.ids.image.ripple_duration_in_fast = 0.05 + self.ids.image.ripple_duration_in_fast = 0.05 def on_transform_in(self, instance_hero_widget, duration): - Animation( - radius=[0, 0, 0, 0], - box_radius=[0, 0, 0, 0], - duration=duration, - ).start(instance_hero_widget) + for instance in [ + instance_hero_widget, + instance_hero_widget._overlay_container, + instance_hero_widget._image, + ]: + Animation(radius=[0, 0, 0, 0], duration=duration).start(instance) def on_transform_out(self, instance_hero_widget, duration): - Animation( - radius=[24, 24, 24, 24], - box_radius=[0, 0, 24, 24], - duration=duration, - ).start(instance_hero_widget) + for instance, radius in { + instance_hero_widget: [dp(24), dp(24), dp(24), dp(24)], + instance_hero_widget._overlay_container: [0, 0, dp(24), dp(24)], + instance_hero_widget._image: [dp(24), dp(24), dp(24), dp(24)], + }.items(): + Animation( + radius=radius, + duration=duration, + ).start(instance) def on_release(self): def switch_screen(*args): @@ -452,7 +491,7 @@ Usage with ScrollView Clock.schedule_once(switch_screen, 0.2) - class Test(MDApp): + class Example(MDApp): def build(self): return Builder.load_string(KV) @@ -466,7 +505,7 @@ Usage with ScrollView self.root.ids.box.add_widget(hero_item) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage-with-scrollview.gif :align: center @@ -496,9 +535,10 @@ Using multiple heroes at the same time x: 24 FitImage: - source: "kivymd/images/logo/kivymd-icon-512.png" + source: "avatar.png" size_hint: None, None size: hero_kivymd.size + radius: self.height / 2 MDHeroFrom: id: hero_kivy @@ -509,18 +549,21 @@ Using multiple heroes at the same time x: 324 FitImage: - source: "data/logo/kivy-icon-512.png" + source: "bg.jpg" size_hint: None, None size: hero_kivy.size + radius: self.height / 2 - MDRaisedButton: - text: "Move Hero To Screen B" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["kivymd", "kivy"] root.current = "screen B" + MDButtonText: + text: "Move Hero To Screen B" + MDScreen: name: "screen B" heroes_to: hero_to_kivymd, hero_to_kivy @@ -538,13 +581,15 @@ Using multiple heroes at the same time size_hint: None, None pos_hint: {"right": 1, "top": 1} - MDRaisedButton: - text: "Move Hero To Screen A" + MDButton: pos_hint: {"center_x": .5} y: "36dp" on_release: root.current_heroes = ["kivy", "kivymd"] root.current = "screen A" + + MDButtonText: + text: "Move Hero To Screen A" ''' @@ -553,7 +598,7 @@ Using multiple heroes at the same time return Builder.load_string(KV) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-multiple-heroes.gif :align: center @@ -575,7 +620,7 @@ class MDHeroFrom(MDBoxLayout): `on_transform_in` when the hero flies from screen **A** to screen **B**. `on_transform_out` - Called when the hero back from screen **B** to screen **A**. + Fired when the hero back from screen **B** to screen **A**. """ tag = StringProperty(allownone=True) @@ -592,10 +637,10 @@ class MDHeroFrom(MDBoxLayout): self.register_event_type("on_transform_out") def on_transform_in(self, *args): - """Called when the hero flies from screen **A** to screen **B**.""" + """Fired when the hero flies from screen **A** to screen **B**.""" def on_transform_out(self, *args): - """Called when the hero back from screen **B** to screen **A**.""" + """Fired when the hero back from screen **B** to screen **A**.""" class MDHeroTo(MDBoxLayout): diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__init__.py index 97c897c..c759278 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__init__.py @@ -1,2 +1,6 @@ # NOQA F401 -from .imagelist import MDSmartTile +from .imagelist import ( + MDSmartTile, + MDSmartTileOverlayContainer, + MDSmartTileImage, +) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/__init__.cpython-311.pyc index aebdd5f..9240cf5 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/imagelist.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/imagelist.cpython-311.pyc index c15356f..f81622d 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/imagelist.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/__pycache__/imagelist.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.kv index fe87f76..cfe2d80 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.kv @@ -1,28 +1,37 @@ - SmartTileImage: - id: image - mipmap: root.mipmap - source: root.source - radius: root.radius if root.radius else [0, 0, 0, 0] - size_hint_y: 1 if root.overlap else None - height: root.height if root.overlap else root.height - box.height - pos: - ((0, 0) if root.overlap else (0, box.height)) \ - if root.box_position == "footer" else \ - (0, 0) - on_release: root.dispatch("on_release") - on_press: root.dispatch("on_press") - _no_ripple_effect: root._no_ripple_effect - SmartTileOverlayBox: - id: box - md_bg_color: root.box_color - size_hint_y: None - padding: "8dp" - radius: root.box_radius - height: "68dp" if root.lines == 2 else "48dp" - pos: - (0, 0) \ - if root.box_position == "footer" else \ - (0, root.height - self.height) + + size_hint_y: + (1 if self._smart_tile.overlap else None) \ + if self._smart_tile else None + height: + ( \ + self._smart_tile.height \ + if self._smart_tile.overlap else \ + self._smart_tile.height - (self._overlay_container.height if self._overlay_container else 0) \ + ) \ + if self._smart_tile else 0 + pos: + ( \ + ( \ + (0, 0) \ + if self._smart_tile.overlap else \ + (0, (self._overlay_container.height if self._overlay_container else 0)) \ + ) \ + if self._smart_tile.overlay_mode == "footer" else \ + (0, 0) \ + ) \ + if self._smart_tile else (0, 0) + on_release: self._smart_tile.dispatch("on_release") + on_press: self._smart_tile.dispatch("on_press") + + + + pos: + ( \ + (0, 0) \ + if self._smart_tile.overlay_mode == "footer" else \ + (0, self._smart_tile.height - self.height) \ + ) \ + if self._smart_tile else (0, 0) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.py index ba84b14..88092cf 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/imagelist/imagelist.py @@ -11,11 +11,32 @@ Components/ImageList .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list.png :align: center -`KivyMD` provides the following tile classes for use: - Usage ----- +.. code-block:: kv + + MDSmartTile: + [...] + + MDSmartTileImage: + [...] + + MDSmartTileOverlayContainer: + [...] + + # Content + [...] + +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list-anatomy.png + :align: center + +Example +------- + .. code-block:: python from kivy.lang import Builder @@ -24,69 +45,103 @@ Usage KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor MDSmartTile: - radius: 24 - box_radius: [0, 0, 24, 24] - box_color: 1, 1, 1, .2 - source: "cats.jpg" pos_hint: {"center_x": .5, "center_y": .5} size_hint: None, None size: "320dp", "320dp" + overlap: False - MDIconButton: - icon: "heart-outline" - theme_icon_color: "Custom" - icon_color: 1, 0, 0, 1 - pos_hint: {"center_y": .5} - on_release: self.icon = "heart" if self.icon == "heart-outline" else "heart-outline" + MDSmartTileImage: + source: "bg.jpg" + radius: [dp(24), dp(24), 0, 0] - MDLabel: - text: "Julia and Julie" - bold: True - color: 1, 1, 1, 1 + MDSmartTileOverlayContainer: + md_bg_color: 0, 0, 0, .5 + adaptive_height: True + padding: "8dp" + spacing: "8dp" + radius: [0, 0, dp(24), dp(24)] + + MDIconButton: + icon: "heart-outline" + theme_icon_color: "Custom" + icon_color: 1, 0, 0, 1 + pos_hint: {"center_y": .5} + on_release: + self.icon = "heart" \\ + if self.icon == "heart-outline" else \\ + "heart-outline" + + MDLabel: + text: "Ibanez GRG121DX-BKF" + theme_text_color: "Custom" + text_color: "white" ''' - class MyApp(MDApp): + class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" return Builder.load_string(KV) - MyApp().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-usage.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list-example.png :align: center -Implementation --------------- +API break +========= -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-usage-sceleton.png - :align: center +1.2.0 version +------------- + +.. code-block:: kv + + MDSmartTile: + [...] + + # Content. + MDIconButton: + [...] + + MDLabel: + [...] + + +2.0.0 version +------------- + +.. code-block:: kv + + MDSmartTile: + [...] + + MDSmartTileImage: + [...] + + MDSmartTileOverlayContainer: + [...] + + # Content. + [...] """ -__all__ = [ - "MDSmartTile", -] +__all__ = ["MDSmartTile", "MDSmartTileOverlayContainer", "MDSmartTileImage"] import os from kivy.clock import Clock from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ColorProperty, - OptionProperty, - StringProperty, - VariableListProperty, -) +from kivy.properties import BooleanProperty, OptionProperty, ObjectProperty from kivy.uix.behaviors import ButtonBehavior from kivymd import uix_path from kivymd.uix.behaviors import RectangularRippleBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.fitimage import FitImage -from kivymd.uix.label import MDLabel from kivymd.uix.relativelayout import MDRelativeLayout with open( @@ -95,12 +150,39 @@ with open( Builder.load_string(kv_file.read()) -class SmartTileImage(RectangularRippleBehavior, ButtonBehavior, FitImage): - """Implements the tile image.""" +class MDSmartTileImage(RectangularRippleBehavior, ButtonBehavior, FitImage): + """ + Implements the tile image. + + .. versionchanged:: 2.0.0 + + The `SmartTileImage` class has been renamed to `MDSmartTileImage`. + + For more information, see in the + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.fitimage.fitimage.FitImage` + classes documentation. + """ + + _smart_tile = ObjectProperty() + _overlay_container = ObjectProperty() -class SmartTileOverlayBox(MDBoxLayout): - """Implements a container for custom widgets to be added to the tile.""" +class MDSmartTileOverlayContainer(MDBoxLayout): + """ + Implements a container for custom widgets to be added to the tile. + + .. versionchanged:: 2.0.0 + + The `SmartTileOverlayBox` class has been renamed to + `MDSmartTileOverlayContainer`. + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + _smart_tile = ObjectProperty() class MDSmartTile(MDRelativeLayout): @@ -108,73 +190,33 @@ class MDSmartTile(MDRelativeLayout): A tile for more complex needs. For more information, see in the - :class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation. + :class:`~kivymd.uix.relativelayout.MDRelativeLayout` + class documentation. Includes an image, a container to place overlays and a box that can act as a header or a footer, as described in the Material Design specs. :Events: `on_press` - Called when the button is pressed. + Fired when the button is pressed. `on_release` - Called when the button is released (i.e. the touch/click that + Fired when the button is released (i.e. the touch/click that pressed the button goes away). """ - box_radius = VariableListProperty([0], length=4) - """ - Box radius. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDSmartTile: - radius: 24 - box_radius: [0, 0, 24, 24] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-box-radius.png - :align: center - - :attr:`box_radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - box_color = ColorProperty((0, 0, 0, 0.5)) - """ - Sets the color in (r, g, b, a) or string format and opacity for the - information box. - - .. code-block:: kv - - MDSmartTile: - radius: 24 - box_radius: [0, 0, 24, 24] - box_color: 0, 1, 0, .5 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-box-color.png - :align: center - - :attr:`box_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `(0, 0, 0, 0.5)`. - """ - - box_position = OptionProperty("footer", options=["footer", "header"]) + overlay_mode = OptionProperty("footer", options=["footer", "header"]) """ Determines weather the information box acts as a header or footer to the image. Available are options: `'footer'`, `'header'`. - .. code-block:: kv + .. versionchanged:: 2.0.0 - MDSmartTile: - radius: 24 - box_radius: [24, 24, 0, 0] - box_position: "header" + The `box_position` attribute has been renamed to `overlay_mode`. - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-box-position.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list-overlay-mode.png :align: center - :attr:`box_position` is a :class:`~kivy.properties.OptionProperty` + :attr:`overlay_mode` is a :class:`~kivy.properties.OptionProperty` and defaults to `'footer'`. """ @@ -182,98 +224,42 @@ class MDSmartTile(MDRelativeLayout): """ Determines if the `header/footer` overlaps on top of the image or not. - .. code-block:: kv - - MDSmartTile: - radius: [24, 24, 0, 0] - box_radius: [0, 0, 24, 24] - overlap: False - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-overlap.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/image-list-overlap.png :align: center :attr:`overlap` is a :class:`~kivy.properties.BooleanProperty` and defaults to `True`. """ - lines = OptionProperty(1, options=[1, 2]) - """ - Number of lines in the `header/footer`. As per `Material Design specs`, - only 1 and 2 are valid values. Available are options: `1`, `2`. - This parameter just increases the height of the container for custom - elements. - - .. code-block:: kv - - MDSmartTile: - radius: 24 - box_radius: [0, 0, 24, 24] - lines: 2 - source: "cats.jpg" - pos_hint: {"center_x": .5, "center_y": .5} - size_hint: None, None - size: "320dp", "320dp" - - MDIconButton: - icon: "heart-outline" - theme_icon_color: "Custom" - icon_color: 1, 0, 0, 1 - pos_hint: {"center_y": .5} - on_release: self.icon = "heart" if self.icon == "heart-outline" else "heart-outline" - - TwoLineListItem: - text: "[color=#ffffff][b]My cats[/b][/color]" - secondary_text: "[color=#808080][b]Julia and Julie[/b][/color]" - pos_hint: {"center_y": .5} - _no_ripple_effect: True - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-smart-tile-lines.png - :align: center - - :attr:`lines` is a :class:`~kivy.properties.OptionProperty` - and defaults to `1`. - """ - - source = StringProperty() - """ - Path to tile image. See :attr:`~kivy.uix.image.Image.source`. - - :attr:`source` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - mipmap = BooleanProperty(False) - """ - Indicate if you want OpenGL mipmapping to be applied to the texture. - Read :ref:`mipmap` for more information. - - .. versionadded:: 1.0.0 - - :attr:`mipmap` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - _no_ripple_effect = BooleanProperty(False) + _overlay_container = ObjectProperty() + _image = ObjectProperty() - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.register_event_type("on_release") self.register_event_type("on_press") def on_release(self, *args): """ - Called when the button is released (i.e. the touch/click that + Fired when the button is released (i.e. the touch/click that pressed the button goes away). """ def on_press(self, *args): - """Called when the button is pressed.""" + """Fired when the button is pressed.""" def add_widget(self, widget, *args, **kwargs): - if isinstance(widget, (SmartTileImage, SmartTileOverlayBox)): + def set_overlay_container(_widget): + _widget._overlay_container = self._overlay_container + + if isinstance(widget, MDSmartTileOverlayContainer): + widget._smart_tile = self + self._overlay_container = widget + return super().add_widget(widget, *args, **kwargs) + elif isinstance(widget, MDSmartTileImage): + self._image = widget + widget._smart_tile = self + widget._overlay_container = self._overlay_container + Clock.schedule_once(lambda x: set_overlay_container(widget), 0.5) return super().add_widget(widget, *args, **kwargs) - else: - if isinstance(widget, MDLabel): - widget.shorten = True - widget.shorten_from = "right" - Clock.schedule_once(lambda x: self.ids.box.add_widget(widget)) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/__init__.cpython-311.pyc index 9bb1425..1fec410 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/label.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/label.cpython-311.pyc index 12bb3fd..a11c520 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/label.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/__pycache__/label.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.kv index 7fba348..e6b747a 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.kv @@ -2,67 +2,40 @@ - disabled_color: self.theme_cls.disabled_hint_text_color text_size: (self.width if not self.adaptive_width else None) \ if not self.adaptive_size else None, \ None + color: + self.text_color \ + if self.text_color else \ + self.theme_cls.onSurfaceColor + disabled_color: + app.theme_cls.onSurfaceColor[:-1] + \ + [self.label_opacity_value_disabled_text] + font_size: + self.theme_cls.font_styles[self.font_style][self.role]["font-size"] \ + if self.theme_font_size == "Primary" else self.font_size + line_height: + self.theme_cls.font_styles[self.font_style][self.role]["line-height"] \ + if self.theme_line_height == "Primary" else self.line_height + font_name: + self.theme_cls.font_styles[self.font_style][self.role]["font-name"] \ + if self.theme_font_name == "Primary" else self.font_name -: - canvas: - Color: - rgba: (1, 1, 1, 1) if self.source else (0, 0, 0, 0) - Rectangle: - group: "rectangle" - source: self.source if self.source else None - pos: - self.pos \ - if not self.source else \ - (self.x - self._size[0] / 2, self.y) - size: - self._size \ - if self.source else \ - self.size - + font_style: "Icon" - text: u"{}".format(md_icons[root.icon]) if root.icon in md_icons else "blank" - source: None if root.icon in md_icons else root.icon - - # Badge icon. - MDLabel: - id: badge - font_style: "Icon" - adaptive_size: True - opposite_icon_color: True - color: root.badge_icon_color - text: - u"{}".format(md_icons[root.badge_icon]) \ - if root.badge_icon in md_icons else \ - "" - pos: - root.x + root.width / 2 + self.width / 2 - dp(6), \ - root.y + self.texture_size[1] / 2 + dp(6) - font_size: - ( \ - root.font_size / 1.5 \ - if not root.badge_font_size else \ - root.badge_font_size \ - ) \ - if root.badge_icon and root.badge_icon != "blank" else 0 - - canvas.before: - Color: - rgba: - ( \ - root.badge_bg_color \ - if root.badge_bg_color else \ - app.theme_cls.error_color \ - ) \ - if root.badge_icon else \ - (0, 0, 0, 0) - RoundedRectangle: - group: "badge" - radius: [self.width / 2,] - pos: self.pos - size: self.size + source: None if self.icon in md_icons else self.icon + adaptive_size: True + text: + ( \ + u"{}".format(md_icons[self.icon]) \ + if self.icon in md_icons else \ + "blank" \ + ) \ + if self.font_name == "Icons" else self.icon + color: + self.icon_color \ + if self.icon_color else \ + self.theme_cls.onSurfaceVariantColor diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.py index 73bd46b..5c9dfa6 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/label/label.py @@ -2,7 +2,7 @@ Components/Label ================ -.. rubric:: The :class:`MDLabel` widget is for rendering text. +.. rubric:: The `MDLabel` widget is for rendering text. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label.png :align: center @@ -11,12 +11,12 @@ Components/Label - MDIcon_ .. MDLabel: + MDLabel ------- -Class :class:`MDLabel` inherited from the :class:`~kivy.uix.label.Label` class -but for :class:`MDLabel` the ``text_size`` parameter is ``(self.width, None)`` -and default is positioned on the left: +Example +------- .. tabs:: @@ -30,20 +30,21 @@ and default is positioned on the left: KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor MDLabel: text: "MDLabel" + halign: "center" ''' - class Test(MDApp): + class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) - Test().run() + Example().run() .. tab:: Declarative Python style @@ -59,83 +60,24 @@ and default is positioned on the left: class Test(MDApp): def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" return ( MDScreen( MDLabel( - text="MDLabel" - ) + text="MDLabel", + halign="center", + ), + md_bg_color=self.theme_cls.backgroundColor, ) ) Test().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-left.png - :align: center - -.. Note:: See :attr:`~kivy.uix.label.Label.halign` - and :attr:`~kivy.uix.label.Label.valign` attributes - of the :class:`~kivy.uix.label.Label` class - -.. code-block:: kv - - MDLabel: - text: "MDLabel" - halign: "center" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-to-center.png - :align: center - -:class:`~MDLabel` color: ------------------------- - -:class:`~MDLabel` provides standard color themes for label color management: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.label import MDLabel - - KV = ''' - MDBoxLayout: - orientation: "vertical" - ''' - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - screen = Builder.load_string(KV) - - # Names of standard color themes. - for name_theme in [ - "Primary", - "Secondary", - "Hint", - "Error", - "ContrastParentBackground", - ]: - screen.add_widget( - MDLabel( - text=name_theme, - halign="center", - theme_text_color=name_theme, - ) - ) - return screen - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-theme-text-color.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label-example.png :align: center To use a custom color for :class:`~MDLabel`, use a theme `'Custom'`. -After that, you can specify the desired color in the ``rgba`` format -in the ``text_color`` parameter: +After that, you can specify the desired color in the ``text_color`` parameter: .. code-block:: kv @@ -143,53 +85,93 @@ in the ``text_color`` parameter: text: "Custom color" halign: "center" theme_text_color: "Custom" - text_color: "blue" + text_color: "red" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-custom-color.png :align: center :class:`~MDLabel` provides standard font styles for labels. To do this, specify the name of the desired style in the :attr:`~MDLabel.font_style` -parameter: +and :attr:`~MDLabel.role` parameters: + +.. code-block:: kv + + MDLabel: + text: "Display, role - 'large'" + font_style: "Display" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style-display-large.png + :align: center + +.. code-block:: kv + + MDLabel: + text: "Display, role - 'small'" + font_style: "Display" + role: "small" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style-display-small.png + :align: center + +.. seealso:: + + `Material Design spec, Typography `_ + + +All styles +---------- .. code-block:: python from kivy.lang import Builder - from kivymd.app import MDApp - from kivymd.uix.label import MDLabel from kivymd.font_definitions import theme_font_styles + from kivymd.app import MDApp KV = ''' - MDScrollView: + MDScreen: + md_bg_color: self.theme_cls.backgroundColor - MDList: - id: box - spacing: "8dp" + MDRecycleView: + id: rv + key_viewclass: 'viewclass' + key_size: 'height' + + RecycleBoxLayout: + padding: dp(10) + spacing: dp(10) + default_size: None, dp(48) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + orientation: "vertical" ''' - class Test(MDApp): + class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" - screen = Builder.load_string(KV) + return Builder.load_string(KV) - # Names of standard font styles. - for name_style in theme_font_styles[:-1]: - screen.ids.box.add_widget( - MDLabel( - text=f"{name_style} style", - halign="center", - font_style=name_style, - adaptive_height=True, - ) - ) - return screen + def on_start(self): + for style in theme_font_styles: + if style != "Icon": + for role in theme_font_styles[style]: + font_size = int(theme_font_styles[style][role]["font-size"]) + self.root.ids.rv.data.append( + { + "viewclass": "MDLabel", + "text": f"{style} {role} {font_size} sp", + "adaptive_height": "True", + "font_style": style, + "role": role, + } + ) - Test().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-label-font-style.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/label-font-style-preview.png :align: center Highlighting and copying labels @@ -210,11 +192,12 @@ You can highlight labels by double tap on the label: KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor MDLabel: adaptive_size: True pos_hint: {"center_x": .5, "center_y": .5} - text: "MDLabel" + text: "Do a double click on me" allow_selection: True padding: "4dp", "4dp" ''' @@ -223,7 +206,6 @@ You can highlight labels by double tap on the label: class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) @@ -233,7 +215,7 @@ You can highlight labels by double tap on the label: .. code-block:: python - from kivy.lang.builder import Builder + from kivy.clock import Clock from kivymd.app import MDApp from kivymd.uix.label import MDLabel @@ -241,18 +223,23 @@ You can highlight labels by double tap on the label: class Example(MDApp): + def on_start(self): + def on_start(dt): + self.root.md_bg_color = self.theme_cls.backgroundColor + + Clock.schedule_once(on_start) + def build(self): self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" return ( MDScreen( MDLabel( adaptive_size=True, - pos_hint={"center_x": .5, "center_y": .5}, - text="MDLabel", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + text="Do a double click on me", allow_selection=True, padding=("4dp", "4dp"), - ) + ), ) ) @@ -345,32 +332,31 @@ Example of copying/cutting labels using the context menu from kivy.lang.builder import Builder from kivy.metrics import dp + from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText from kivymd.app import MDApp from kivymd.uix.label import MDLabel from kivymd.uix.menu import MDDropdownMenu - from kivymd.toast import toast KV = ''' MDBoxLayout: orientation: "vertical" spacing: "12dp" padding: "24dp" + md_bg_color: self.theme_cls.backgroundColor - MDScrollView: - - MDBoxLayout: - id: box - orientation: "vertical" - padding: "24dp" - spacing: "12dp" - adaptive_height: True + MDBoxLayout: + id: box + orientation: "vertical" + padding: "24dp" + spacing: "12dp" + adaptive_height: True MDTextField: max_height: "200dp" - mode: "fill" + mode: "filled" multiline: True - MDWidget: + Widget: ''' data = [ @@ -381,34 +367,37 @@ Example of copying/cutting labels using the context menu "Nisl rhoncus mattis rhoncus urna neque. Orci nulla pellentesque " "dignissim enim. Ac auctor augue mauris augue neque gravida in fermentum. " "Lacus suspendisse faucibus interdum posuere." - ] + def toast(text): + MDSnackbar( + MDSnackbarText( + text=text, + ), + y=dp(24), + pos_hint={"center_x": 0.5}, + size_hint_x=0.3, + ).open() + + class CopyLabel(MDLabel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.allow_selection = True self.adaptive_height = True - self.theme_text_color = "Custom" - self.text_color = self.theme_cls.text_color class Example(MDApp): context_menu = None def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" return Builder.load_string(KV) def on_start(self): for text in data: copy_label = CopyLabel(text=text) - copy_label.bind( - on_selection=self.open_context_menu, - on_cancel_selection=self.restore_text_color, - ) + copy_label.bind(on_selection=self.open_context_menu) self.root.ids.box.add_widget(copy_label) def click_item_context_menu( @@ -424,24 +413,17 @@ Example of copying/cutting labels using the context menu if self.context_menu: self.context_menu.dismiss() - def restore_text_color(self, instance_label: CopyLabel) -> None: - instance_label.text_color = self.theme_cls.text_color - def open_context_menu(self, instance_label: CopyLabel) -> None: instance_label.text_color = "black" menu_items = [ { "text": "Copy text", - "viewclass": "OneLineListItem", - "height": dp(48), "on_release": lambda: self.click_item_context_menu( "copy", instance_label ), }, { "text": "Cut text", - "viewclass": "OneLineListItem", - "height": dp(48), "on_release": lambda: self.click_item_context_menu( "cut", instance_label ), @@ -459,6 +441,7 @@ Example of copying/cutting labels using the context menu :align: center .. MDIcon: + MDIcon ------- @@ -481,7 +464,6 @@ The :class:`~MDIcon` class is inherited from MDIcon: icon: "gmail" - pos_hint: {"center_x": .5, "center_y": .5} .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon.png :align: center @@ -493,11 +475,87 @@ MDIcon with badge icon MDIcon: icon: "gmail" - badge_icon: "numeric-10" - pos_hint: {"center_x": .5, "center_y": .5} + + MDBadge: + text: "10+" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-badge.png :align: center + +MDIcon with a custom font icon +------------------------------ + +You can use custom fonts to display icons. Such as for example +`Material Symbols `_. You can find the +necessary fonts in the +`materialsymbols-python `_ +repository + +.. code-block:: python + + from kivy.core.text import LabelBase + from kivy.lang import Builder + from kivy.metrics import sp + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDIcon: + icon: "music_video" + theme_font_name: "Custom" + font_name: "MaterialSymbols" + pos_hint: {"center_x": .5, "center_y": .58} + + MDButton: + pos_hint: {"center_x": .5, "center_y": .47} + + MDButtonIcon: + icon: "music_video" + theme_font_name: "Custom" + font_name: "MaterialSymbols" + + MDButtonText: + text: "Elevated" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + + LabelBase.register( + name="MaterialSymbols", + fn_regular="Material_Symbols_Outlined-20-200-1_200.ttf", + ) + + self.theme_cls.font_styles["MaterialSymbols"] = { + "large": { + "line-height": 1.64, + "font-name": "MaterialSymbols", + "font-size": sp(57), + }, + "medium": { + "line-height": 1.52, + "font-name": "MaterialSymbols", + "font-size": sp(45), + }, + "small": { + "line-height": 1.44, + "font-name": "MaterialSymbols", + "font-size": sp(36), + }, + } + + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-icon-castom-font.png + :align: center """ from __future__ import annotations @@ -505,45 +563,34 @@ from __future__ import annotations __all__ = ("MDLabel", "MDIcon") import os -from typing import Union from kivy.animation import Animation from kivy.clock import Clock from kivy.core.clipboard import Clipboard from kivy.core.window import Window -from kivy.graphics import Color, Rectangle +from kivy.graphics import Color, SmoothRoundedRectangle from kivy.lang import Builder -from kivy.metrics import sp + from kivy.properties import ( - AliasProperty, BooleanProperty, ColorProperty, - ListProperty, - NumericProperty, ObjectProperty, - OptionProperty, StringProperty, + VariableListProperty, + OptionProperty, ) from kivy.uix.label import Label from kivymd import uix_path from kivymd.theming import ThemableBehavior -from kivymd.theming_dynamic_text import get_contrast_text_color from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior, TouchBehavior -from kivymd.uix.floatlayout import MDFloatLayout +from kivymd.uix.behaviors import ( + DeclarativeBehavior, + TouchBehavior, + BackgroundColorBehavior, +) +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior -__MDLabel_colors__ = { - "Primary": "text_color", - "Secondary": "secondary_text_color", - "Hint": "disabled_hint_text_color", - "Error": "error_color", - "OP": { - "Primary": "opposite_text_color", - "Secondary": "opposite_secondary_text_color", - "Hint": "opposite_disabled_hint_text_color", - }, -} with open( os.path.join(uix_path, "label", "label.kv"), encoding="utf-8" @@ -554,80 +601,68 @@ with open( class MDLabel( DeclarativeBehavior, ThemableBehavior, + BackgroundColorBehavior, Label, MDAdaptiveWidget, TouchBehavior, + StateLayerBehavior, ): """ Label class. For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and :class:`~kivy.uix.label.Label` and :class:`~kivymd.uix.MDAdaptiveWidget` and - :class:`~kivymd.uix.behaviors.TouchBehavior` + :class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` classes documentation. :Events: `on_ref_press` - Called when the user clicks on a word referenced with a + Fired when the user clicks on a word referenced with a ``[ref]`` tag in a text markup. `on_copy` - Called when double-tapping on the label. + Fired when double-tapping on the label. `on_selection` - Called when double-tapping on the label. + Fired when double-tapping on the label. `on_cancel_selection` - Called when the highlighting is removed from the label text. + Fired when the highlighting is removed from the label text. """ - font_style = StringProperty("Body1") + font_style = StringProperty("Body") """ Label font style. - Available vanilla font_style are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, - `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`, - `'Caption'`, `'Overline'`, `'Icon'`. + .. versionchanged:: 2.0.0 + + Available vanilla font_style are: `'Display'`, `'Headline'`, `'Title'`, + `'Label'`, `'Body'``. :attr:`font_style` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. + and defaults to `'Body'`. """ - _capitalizing = BooleanProperty(False) - - def _get_text(self): - if self._capitalizing: - return self._text.upper() - return self._text - - def _set_text(self, value): - self._text = value - - _text = StringProperty() - - text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"]) - """Text of the label.""" - - theme_text_color = OptionProperty( - "Primary", - allownone=True, - options=[ - "Primary", - "Secondary", - "Hint", - "Error", - "Custom", - "ContrastParentBackground", - ], - ) + role = OptionProperty("large", options=["large", "medium", "small"]) """ - Label color scheme name. + Role of font style. - Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`, - `'Custom'`, `'ContrastParentBackground'`. + .. versionadded:: 2.0.0 - :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. + Available options are: `'large'`, `'medium'`, `'small'`. + + :attr:`role` is an :class:`~kivy.properties.StringProperty` + and defaults to `'large'`. + """ + + text = StringProperty() + """ + Text of the label. + + :attr:`text` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. """ text_color = ColorProperty(None) @@ -690,70 +725,48 @@ class MDLabel( and defaults to `False`. """ - _text_color_str = StringProperty() + radius = VariableListProperty([0], length=4) + """ + Label radius. - parent_background = ColorProperty(None) - can_capitalize = BooleanProperty(True) - canvas_bg = ObjectProperty() + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ + + _canvas_bg = ObjectProperty(allownone=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.bind( - font_style=self.update_font_style, - can_capitalize=self.update_font_style, - ) - self.theme_cls.bind(theme_style=self._do_update_theme_color) self.register_event_type("on_copy") self.register_event_type("on_selection") self.register_event_type("on_cancel_selection") - self.on_theme_text_color(None, self.theme_text_color) - self.update_font_style(None, "") - self.on_opposite_colors(None, self.opposite_colors) - Clock.schedule_once(self.check_font_styles) - - def check_font_styles(self, interval: Union[int, float] = 0) -> bool: - if self.font_style not in list(self.theme_cls.font_styles.keys()): - raise ValueError( - f"MDLabel.font_style is set to an invalid option '{self.font_style}'." - f"Must be one of: {list(self.theme_cls.font_styles)}" - ) - else: - return True - - def update_font_style(self, instance_label, font_style: str) -> None: - if self.check_font_styles() is True: - font_info = self.theme_cls.font_styles[self.font_style] - self.font_name = font_info[0] - if self.font_style in list(self.theme_cls.font_styles.keys())[0:14]: - self.font_size = sp(font_info[1]) - - if font_info[2] and self.can_capitalize: - self._capitalizing = True - else: - self._capitalizing = False - - # TODO: Add letter spacing change - # self.letter_spacing = font_info[3] def do_selection(self) -> None: if not self.is_selected: self.md_bg_color = ( - self.theme_cls.primary_light + self.theme_cls.secondaryContainerColor if not self.color_selection else self.color_selection ) def cancel_selection(self) -> None: if self.is_selected: + self.canvas.before.remove_group("md-label-selection-color") + self.canvas.before.remove_group( + "md-label-selection-color-rectangle" + ) self.md_bg_color = ( - self.theme_cls.bg_normal + self.parent.md_bg_color if not self.color_deselection else self.color_deselection ) self.dispatch("on_cancel_selection") self.is_selected = False + self._canvas_bg = None def on_double_tap(self, touch, *args) -> None: + """Fired by double-clicking on the widget.""" + if self.allow_copy and self.collide_point(*touch.pos): Clipboard.copy(self.text) self.dispatch("on_copy") @@ -762,73 +775,44 @@ class MDLabel( self.dispatch("on_selection") self.is_selected = True - def on_window_touch(self, *args): + def on_window_touch(self, *args) -> None: + """Fired at the on_touch_down event.""" + if self.is_selected: self.cancel_selection() def on_copy(self, *args) -> None: """ - Called when double-tapping on the label. + Fired when double-tapping on the label. .. versionadded:: 1.2.0 """ def on_selection(self, *args) -> None: """ - Called when double-tapping on the label. + Fired when double-tapping on the label. .. versionadded:: 1.2.0 """ def on_cancel_selection(self, *args) -> None: """ - Called when the highlighting is removed from the label text. + Fired when the highlighting is removed from the label text. .. versionadded:: 1.2.0 """ def on_allow_selection(self, instance_label, selection: bool) -> None: + """Fired when the :attr:`allow_selection` value changes.""" + if selection: Window.bind(on_touch_down=self.on_window_touch) else: Window.unbind(on_touch_down=self.on_window_touch) - def on_theme_text_color( - self, instance_label, theme_text_color: str - ) -> None: - op = self.opposite_colors - if op: - self._text_color_str = __MDLabel_colors__.get("OP", "").get( - theme_text_color, "" - ) - else: - self._text_color_str = __MDLabel_colors__.get(theme_text_color, "") - if self._text_color_str: - self._do_update_theme_color() - else: - # 'Custom' and 'ContrastParentBackground' lead here, as well as the - # generic None value it's not yet been set - self._text_color_str = "" - if theme_text_color == "Custom" and self.text_color: - color = self.text_color - elif ( - theme_text_color == "ContrastParentBackground" - and self.parent_background - ): - color = get_contrast_text_color(self.parent_background) - else: - color = [0, 0, 0, 1] + def on_text_color(self, instance_label, color: list | str) -> None: + """Fired when the :attr:`text_color` value changes.""" - if self.theme_cls.theme_style_switch_animation: - Animation( - color=color, - d=self.theme_cls.theme_style_switch_animation_duration, - t="linear", - ).start(self) - else: - self.color = color - - def on_text_color(self, instance_label, color: Union[list, str]) -> None: if self.theme_text_color == "Custom": if self.theme_cls.theme_style_switch_animation: Animation( @@ -839,96 +823,61 @@ class MDLabel( else: self.color = self.text_color - def on_opposite_colors(self, *args) -> None: - self.on_theme_text_color(self, self.theme_text_color) + def on_md_bg_color(self, instance_label, color: list | str) -> None: + """Fired when the :attr:`md_bg_color` value changes.""" - def on_md_bg_color(self, instance_label, color: Union[list, str]) -> None: - self.canvas.remove_group("Background_instruction") - self.canvas.before.clear() - with self.canvas.before: - Color(rgba=color) - self.canvas_bg = Rectangle(pos=self.pos, size=self.size) - self.bind(pos=self.update_canvas_bg_pos) + def on_md_bg_color(*args) -> None: + from kivymd.uix.selectioncontrol import MDCheckbox + from kivymd.uix.tooltip import MDTooltipPlain + + if not issubclass( + self.__class__, (MDCheckbox, MDIcon, MDTooltipPlain) + ): + self.canvas.remove_group("Background_instruction") + + # FIXME: IndexError + # try: + # self.canvas.before.clear() + # except IndexError: + # pass + + with self.canvas.before: + Color(rgba=color, group="md-label-selection-color") + self._canvas_bg = SmoothRoundedRectangle( + pos=self.pos, + size=self.size, + radius=self.radius, + group="md-label-selection-color-rectangle", + ) + self.bind(pos=self.update_canvas_bg_pos) + + Clock.schedule_once(on_md_bg_color) def on_size(self, instance_label, size: list) -> None: - if self.canvas_bg: - self.canvas_bg.size = size + """Fired when the parent window of the application is resized.""" + + if self._canvas_bg: + self._canvas_bg.size = size def update_canvas_bg_pos(self, instance_label, pos: list) -> None: - if self.canvas_bg: - self.canvas_bg.pos = pos - - def _do_update_theme_color(self, *args): - if self._text_color_str: - if not self.disabled: - color = getattr(self.theme_cls, self._text_color_str) - else: - color = getattr(self.theme_cls, "disabled_hint_text_color") - - if self.theme_cls.theme_style_switch_animation: - Animation( - color=color, - d=self.theme_cls.theme_style_switch_animation_duration, - t="linear", - ).start(self) - else: - self.color = color + if self._canvas_bg: + self._canvas_bg.pos = pos -class MDIcon(MDFloatLayout, MDLabel): +class MDIcon(MDLabel): """ Icon class. - For more information, see in the :class:`~MDLabel` and - :class:`~kivymd.uix.floatlayout.MDFloatLayout` classes documentation. + For more information, see in the + :class:`~MDLabel` class documentation. """ - icon = StringProperty("android") + icon = StringProperty("blank") """ Label icon name. :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - badge_icon = StringProperty() - """ - Label badge icon name. - - .. versionadded:: 1.0.0 - - :attr:`badge_icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - badge_icon_color = ColorProperty([1, 1, 1, 1]) - """ - Badge icon color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - :attr:`badge_icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - badge_bg_color = ColorProperty(None) - """ - Badge icon background color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - :attr:`badge_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - badge_font_size = NumericProperty() - """ - Badge font size. - - .. versionadded:: 1.0.0 - - :attr:`badge_font_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. + and defaults to `'blank'`. """ source = StringProperty(None, allownone=True) @@ -939,16 +888,33 @@ class MDIcon(MDFloatLayout, MDLabel): and defaults to `None`. """ - _size = ListProperty((0, 0)) + icon_color = ColorProperty(None) + """ + Icon color in (r, g, b, a) or string format. - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - Clock.schedule_once(self.adjust_size) + .. versionadded:: 2.0.0 - def adjust_size(self, *args) -> None: - from kivymd.uix.selectioncontrol import MDCheckbox + :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ - if not isinstance(self, MDCheckbox): - self.size_hint = (None, None) - self._size = self.texture_size[1], self.texture_size[1] - self.adaptive_size = True + icon_color_disabled = ColorProperty(None) + """ + The icon color in (r, g, b, a) or string format of the button when + the button is disabled. + + .. versionadded:: 2.0.0 + + :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # kivymd.uix.badge.badge.MDBadge object. + _badge = ObjectProperty() + + def add_widget(self, widget, index=0, canvas=None): + from kivymd.uix.badge import MDBadge + + if isinstance(widget, MDBadge): + self._badge = widget + return super().add_widget(widget) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__init__.py index a0e061c..096715a 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__init__.py @@ -1,33 +1,16 @@ # NOQA F401 from .list import ( - BaseListItem, - CheckboxLeftWidget, - IconLeftWidget, - IconLeftWidgetWithoutTouch, - IconRightWidget, - IconRightWidgetWithoutTouch, - ILeftBody, - ILeftBodyTouch, - ImageLeftWidget, - ImageLeftWidgetWithoutTouch, - ImageRightWidget, - ImageRightWidgetWithoutTouch, - IRightBody, - IRightBodyTouch, MDList, - OneLineAvatarIconListItem, - OneLineAvatarListItem, - OneLineIconListItem, - OneLineListItem, - OneLineRightIconListItem, - ThreeLineAvatarIconListItem, - ThreeLineAvatarListItem, - ThreeLineIconListItem, - ThreeLineListItem, - ThreeLineRightIconListItem, - TwoLineAvatarIconListItem, - TwoLineAvatarListItem, - TwoLineIconListItem, - TwoLineListItem, - TwoLineRightIconListItem, + MDListItem, + BaseListItem, + BaseListItemText, + BaseListItemIcon, + MDListItemLeadingIcon, + MDListItemTrailingIcon, + MDListItemHeadlineText, + MDListItemTertiaryText, + MDListItemLeadingAvatar, + MDListItemSupportingText, + MDListItemTrailingCheckbox, + MDListItemTrailingSupportingText, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/__init__.cpython-311.pyc index 6f1b10c..3ed52d0 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/list.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/list.cpython-311.pyc index 84febf7..f95b637 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/list.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/__pycache__/list.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.kv index 467d1c8..2682a45 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.kv @@ -1,173 +1,153 @@ -#:import m_res kivymd.material_resources - - cols: 1 adaptive_height: True padding: 0, self._list_vertical_padding - - size_hint_y: None - - canvas: + + # Divider. + canvas.after: Color: rgba: ( \ - self.theme_cls.divider_color \ - if root.divider is not None \ - else (0, 0, 0, 0) \ - ) \ - if not root.divider_color \ - else \ - root.divider_color - - Line: - points: ( \ - root.x ,root.y, root.x + self.width, root.y) \ - if root.divider == "Full" else \ - (root.x + root._txt_left_pad, root.y, \ - root.x + self.width - root._txt_left_pad-root._txt_right_pad, \ - root.y \ - ) - Color: - rgba: root.bg_color if root.bg_color else (0, 0, 0, 0) - RoundedRectangle: - pos: self.pos - size: self.size - radius: root.radius + self.theme_cls.surfaceVariantColor \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor \ + ) \ + if self.theme_divider_color == "Primary" else \ + self.divider_color + ) \ + + if self.divider else self.theme_cls.transparentColor + Line: + width: 1 + points: self.x ,self.y, self.x + self.width, self.y + + size_hint_y: None + spacing: "16dp" + padding: + "16dp", \ + "12dp" if len(text_container.children) == 3 else "8dp", \ + "24dp", \ + "12dp" if len(text_container.children) == 3 else "8dp" + # FIXME: The design of the material suggests specifying the + # background color of the disabled widget as the "onSurface" + # color when hovering the mouse cursor. But this color is very + # dark/light. So I chose the color "onSurfaceColor" color with 12 + # percent transparency. + md_bg_color: + self.theme_cls.surfaceColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color + height: + { \ + 0: "100dp", \ + 1: "56dp", \ + 2: "72dp", \ + 3: "88dp", \ + } \ + [len(text_container.children)] + on_disabled: + if leading_container.children: \ + leading_container.children[0].disabled = args[1] + BoxLayout: - id: _text_container - orientation: "vertical" - pos: root.pos - padding: - root._txt_left_pad, root._txt_top_pad, \ - root._txt_right_pad, root._txt_bot_pad + id: leading_container + size_hint_x: None + width: 0 - MDLabel: - id: _lbl_primary - text: root.text - font_style: root.font_style - theme_text_color: root.theme_text_color - text_color: root.text_color + AnchorLayout: + anchor_y: "center" + + BoxLayout: + id: text_container + orientation: "vertical" size_hint_y: None - height: self.texture_size[1] - markup: True - shorten_from: "right" - shorten: True - - MDLabel: - id: _lbl_secondary - text: "" if root._num_lines == 1 else root.secondary_text - font_style: root.secondary_font_style - theme_text_color: root.secondary_theme_text_color - text_color: root.secondary_text_color - size_hint_y: None - height: 0 if root._num_lines == 1 else self.texture_size[1] - shorten: True - shorten_from: "right" - markup: True - - MDLabel: - id: _lbl_tertiary - text: "" if root._num_lines == 1 else root.tertiary_text - font_style: root.tertiary_font_style - theme_text_color: root.tertiary_theme_text_color - text_color: root.tertiary_text_color - size_hint_y: None - height: 0 if root._num_lines == 1 else self.texture_size[1] - shorten: True - shorten_from: "right" - markup: True - - - + height: self.minimum_height + spacing: "2dp" + on_children: + if leading_container.children: \ + leading_container.children[0].pos_hint = {"top": 1} \ + if len(args[1]) == 3 else {"center_y": .5} BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height / 2 - self.height / 2 - size: dp(40), dp(40) + id: trailing_container + size_hint_x: None + width: 0 + on_children: + if text_container.children and self.children: \ + self.children[0].pos_hint = {"top": 1} \ + if len(text_container.children) == 3 else {"center_y": .5} - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height - root._txt_top_pad - self.height - dp(5) - size: dp(40), dp(40) + + size_hint: None, None + size: "24dp", "24dp" + text_color: + ( \ + self.theme_cls.onSurfaceVariantColor \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color \ + if self.icon_color else \ + self.theme_cls.transparentColor \ + ) \ + ) \ + if not self.disabled else self.disabled_color + disabled_color: + self.theme_cls.onSurfaceColor[:-1] + \ + [self.icon_button_standard_opacity_value_disabled_icon] \ + if not self.icon_color_disabled else self.icon_color_disabled - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) + + adaptive_width: True + font_style: "Label" + role: "small" - - - BoxLayout: - id: _left_container - size_hint: None, None - x: root.x + dp(16) - y: root.y + root.height - root._txt_top_pad - self.height - dp(5) - size: dp(48), dp(48) + + size_hint: None, None + size: "40dp", "40dp" + radius: self.height / 2 + # FIXME: The design of the material suggests specifying the + # background color of the disabled widget as the "onSurface" + # color when hovering the mouse cursor. But this color is very + # dark/light. So I chose the color "onSurfaceColor" color with 12 + # percent transparency. + md_bg_color: + self.theme_cls.primaryContainerColor \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] \ + + ( \ + [self._list_item.list_opacity_value_disabled_leading_avatar] \ + if self._list_item else [0] \ + ) - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) + + adaptive_height: True + markup: True + shorten_from: "right" + font_style: "Body" + role: "medium" + shorten: True + text_color: + self.theme_cls.onSurfaceVariantColor \ + if root.theme_text_color == "Primary" else \ + ( \ + root.text_color \ + if root.text_color else \ + self.theme_cls.onSurfaceVariantColor \ + ) - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height / 2 - self.height / 2 - size: dp(48), dp(48) - - - - - BoxLayout: - id: _right_container - size_hint: None, None - x: root.x + root.width - m_res.HORIZ_MARGINS - self.width - y: root.y + root.height - root._txt_top_pad - self.height - dp(5) - size: dp(48), dp(48) + + font_style: "Body" + role: "large" + bold: True + # FIXME: `RecursionError: maximum recursion depth exceeded while calling + # a Python object` when use `text_color` property. + -text_color: self.theme_cls.onSurfaceColor if root.theme_text_color == "Primary" else (root.text_color if root.text_color else self.theme_cls.onSurfaceColor) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.py index 035ca8a..290e858 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/list/list.py @@ -4,146 +4,51 @@ Components/List .. seealso:: - `Material Design spec, Lists `_ + `Material Design spec, Lists `_ .. rubric:: Lists are continuous, vertical indexes of text or images. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.png :align: center -The class :class:`~MDList` in combination with a :class:`~BaseListItem` like -:class:`~OneLineListItem` will create a list that expands as items are added to -it, working nicely with `Kivy's` :class:`~kivy.uix.scrollview.ScrollView`. - -Due to the variety in sizes and controls in the `Material Design spec`, -this module suffers from a certain level of complexity to keep the widgets -compliant, flexible and performant. - -For this `KivyMD` provides list items that try to cover the most common usecases, -when those are insufficient, there's a base class called :class:`~BaseListItem` -which you can use to create your own list items. This documentation will only -cover the provided ones, for custom implementations please refer to this -module's source code. - -`KivyMD` provides the following list items classes for use: - -Text only ListItems -------------------- - -- OneLineListItem_ -- TwoLineListItem_ -- ThreeLineListItem_ - -ListItems with widget containers --------------------------------- - -These widgets will take other widgets that inherit from :class:`~ILeftBody`, -:class:`ILeftBodyTouch`, :class:`~IRightBody` or :class:`~IRightBodyTouch` and -put them in their corresponding container. - -As the name implies, :class:`~ILeftBody` and :class:`~IRightBody` will signal -that the widget goes into the left or right container, respectively. - -:class:`~ILeftBodyTouch` and :class:`~IRightBodyTouch` do the same thing, -except these widgets will also receive touch events that occur within their -surfaces. - -`KivyMD` provides base classes such as :class:`~ImageLeftWidget`, -:class:`~ImageRightWidget`, :class:`~IconRightWidget`, :class:`~IconLeftWidget`, -based on the above classes. - -.. rubric:: Allows the use of items with custom widgets on the left. - -- OneLineAvatarListItem_ -- TwoLineAvatarListItem_ -- ThreeLineAvatarListItem_ - -- OneLineIconListItem_ -- TwoLineIconListItem_ -- ThreeLineIconListItem_ - -.. rubric:: It allows the use of elements with custom widgets on the left - and the right. - -- OneLineAvatarIconListItem_ -- TwoLineAvatarIconListItem_ -- ThreeLineAvatarIconListItem_ - -- OneLineRightIconListItem_ -- TwoLineRightIconListItem_ -- ThreeLineRightIconListItem_ +- Use lists to help users find a specific item and act on it; +- Order list items in logical ways (like alphabetical or numerical); +- Three sizes: one-line, two-line, and three-line; +- Keep items short and easy to scan; +- Show icons, text, and actions in a consistent format; Usage ----- -.. tabs:: +.. code-block:: kv - .. tab:: Declarative KV style + MDListItem: - .. code-block:: python + MDListItemLeadingIcon: # MDListItemLeadingAvatar - from kivy.lang import Builder + MDListItemHeadlineText: - from kivymd.app import MDApp - from kivymd.uix.list import OneLineListItem + MDListItemSupportingText: - KV = ''' - MDScrollView: + MDListItemTertiaryText: - MDList: - id: container - ''' + MDListItemTrailingIcon: # MDListItemTrailingCheckbox +Anatomy +------- - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - def on_start(self): - for i in range(20): - self.root.ids.container.add_widget( - OneLineListItem(text=f"Single-line item {i}") - ) - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.list import OneLineListItem - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return ( - MDScrollView( - MDList( - id="container" - ) - ) - ) - - def on_start(self): - for i in range(20): - self.root.ids.container.add_widget( - OneLineListItem(text=f"Single-line item {i}") - ) - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/lists-anatomy.png :align: center -Events of List --------------- +Example: +======== + +One line list item +------------------ .. tabs:: - .. tab:: Declarative KV style + .. tab:: Declarative KV styles .. code-block:: python @@ -152,824 +57,318 @@ Events of List from kivymd.app import MDApp KV = ''' - MDScrollView: + MDScreen: + md_bg_color: self.theme_cls.backgroundColor - MDList: + MDListItem: + pos_hint: {"center_x": .5, "center_y": .5} + size_hint_x: .8 - OneLineAvatarIconListItem: - on_release: print("Click!") - - IconLeftWidget: - icon: "github" - - OneLineAvatarIconListItem: - on_release: print("Click 2!") - - IconLeftWidget: - icon: "gitlab" + MDListItemHeadlineText: + text: "Headline" ''' class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" return Builder.load_string(KV) Example().run() - .. tab:: Declarative python style + .. tab:: Declarative Python styles .. code-block:: python + from kivymd.uix.list import MDListItem, MDListItemHeadlineText + from kivymd.uix.screen import MDScreen from kivymd.app import MDApp - from kivymd.uix.scrollview import MDScrollView - from kivymd.uix.list import MDList, OneLineAvatarIconListItem, IconLeftWidget class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" return ( - MDScrollView( - MDList( - OneLineAvatarIconListItem( - IconLeftWidget( - icon="github" - ), - on_release=lambda x: print("Click!") + MDScreen( + MDListItem( + MDListItemHeadlineText( + text="Headline", ), - OneLineAvatarIconListItem( - IconLeftWidget( - icon="gitlab" - ), - on_release=lambda x: print("Click 2!") - ), - ) + pos_hint={"center_x": .5, "center_y": .5}, + size_hint_x=0.8, + ), + md_bg_color=self.theme_cls.backgroundColor, ) ) Example().run() -.. OneLineListItem: -OneLineListItem ---------------- - -.. code-block:: kv - - OneLineListItem: - text: "Single-line item" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineListItem.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-list.gif :align: center -.. TwoLineListItem: -TwoLineListItem ---------------- - -.. code-block:: kv - - TwoLineListItem: - text: "Two-line item" - secondary_text: "Secondary text here" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineListItem.png - :align: center - -.. ThreeLineListItem: -ThreeLineListItem ------------------ - -.. code-block:: kv - - ThreeLineListItem: - text: "Three-line item" - secondary_text: "This is a multi-line label where you can" - tertiary_text: "fit more text than usual" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineListItem.png - :align: center - -.. OneLineAvatarListItem: -OneLineAvatarListItem ---------------------- +Two line list item +------------------ .. tabs:: - .. tab:: Declarative KV style + .. tab:: Declarative KV styles .. code-block:: kv - OneLineAvatarListItem: - text: "Single-line item with avatar" + MDListItem: - ImageLeftWidget: - source: "kivymd/images/logo/kivymd-icon-256.png" + MDListItemHeadlineText: + text: "Headline" - .. tab:: Declarative python style + MDListItemSupportingText: + text: "Supporting text" + + .. tab:: Declarative Python styles .. code-block:: python - OneLineAvatarListItem( - ImageLeftWidget( - source="kivymd/images/logo/kivymd-icon-256.png" + MDListItem( + MDListItemHeadlineText( + text="Headline", + ), + MDListItemSupportingText( + text="Supporting text", ), - text="Single-line item with avatar", ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarListItem.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-list.png :align: center -.. TwoLineAvatarListItem: -TwoLineAvatarListItem ---------------------- +Three line list item +-------------------- .. tabs:: - .. tab:: Declarative KV style + .. tab:: Declarative KV styles .. code-block:: kv - TwoLineAvatarListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" + MDListItem: - ImageLeftWidget: - source: "kivymd/images/logo/kivymd-icon-256.png" + MDListItemHeadlineText: + text: "Headline" - .. tab:: Declarative python style + MDListItemSupportingText: + text: "Supporting text" + + MDListItemTertiaryText: + text: "Tertiary text" + + .. tab:: Declarative Python styles .. code-block:: python - OneLineAvatarListItem( - ImageLeftWidget( - source="kivymd/images/logo/kivymd-icon-256.png" + MDListItem( + MDListItemHeadlineText( + text="Headline", + ), + MDListItemSupportingText( + text="Supporting text", + ), + MDListItemTertiaryText( + text="Tertiary text", ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarListItem.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-list.png :align: center -.. ThreeLineAvatarListItem: -ThreeLineAvatarListItem ------------------------ - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - ThreeLineAvatarListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" - - ImageLeftWidget: - source: "kivymd/images/logo/kivymd-icon-256.png" - - .. tab:: Declarative python style - - .. code-block:: python - - OneLineAvatarListItem( - ImageLeftWidget( - source="kivymd/images/logo/kivymd-icon-256.png" - ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", - tertiary_text: "fit more text than usual" - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarListItem.png - :align: center - -.. OneLineRightIconListItem: -OneLineRightIconListItem ------------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - OneLineRightIconListItem: - text: "Single-line item with avatar" - - ImageRightWidget: - source: "kivymd/images/logo/kivymd-icon-256.png" - - .. tab:: Declarative python style - - .. code-block:: python - - OneLineRightIconListItem( - ImageRightWidget( - source="kivymd/images/logo/kivymd-icon-256.png" - ), - text="Single-line item with avatar", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineRightIconListItem.png - :align: center - -.. TwoLineRightIconListItem: -TwoLineRightIconListItem ------------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - TwoLineRightIconListItem: - text: "Single-line item with avatar" - secondary_text: "Secondary text here" - - ImageRightWidget: - source: "kivymd/images/logo/kivymd-icon-256.png" - - .. tab:: Declarative python style - - .. code-block:: python - - TwoLineRightIconListItem( - ImageRightWidget( - source="kivymd/images/logo/kivymd-icon-256.png" - ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineRightIconListItem.png - :align: center - -.. ThreeLineRightIconListItem: -ThreeLineRightIconListItem --------------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - ThreeLineRightIconListItem: - text: "Single-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" - - ImageRightWidget: - source: "kivymd/images/logo/kivymd-icon-256.png" - - .. tab:: Declarative python style - - .. code-block:: python - - ThreeLineRightIconListItem( - ImageRightWidget( - source="kivymd/images/logo/kivymd-icon-256.png" - ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", - tertiary_text: "fit more text than usual", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineRightIconListItem.png - :align: center - -.. OneLineIconListItem: -OneLineIconListItem -------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - OneLineIconListItem: - text: "Single-line item with avatar" - - IconLeftWidget: - icon: "language-python" - - .. tab:: Declarative python style - - .. code-block:: python - - OneLineIconListItem( - IconLeftWidget( - icon="language-python" - ), - text="Single-line item with avatar" - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineIconListItem.png - :align: center - -.. TwoLineIconListItem: -TwoLineIconListItem -------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - TwoLineIconListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - - IconLeftWidget: - icon: "language-python" - - .. tab:: Declarative python style - - .. code-block:: python - - TwoLineIconListItem( - IconLeftWidget( - icon="language-python" - ), - text="Single-line item with avatar", - secondary_text: "Secondary text here" - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineIconListItem.png - :align: center - -.. ThreeLineIconListItem: -ThreeLineIconListItem ---------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - ThreeLineIconListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" - - IconLeftWidget: - icon: "language-python" - - .. tab:: Declarative python style - - .. code-block:: python - - ThreeLineIconListItem( - IconLeftWidget( - icon="language-python" - ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", - tertiary_text: "fit more text than usual", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineIconListItem.png - :align: center - -.. OneLineAvatarIconListItem: -OneLineAvatarIconListItem -------------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - OneLineAvatarIconListItem: - text: "One-line item with avatar" - - IconLeftWidget: - icon: "plus" - - IconRightWidget: - icon: "minus" - - .. tab:: Declarative python style - - .. code-block:: python - - OneLineAvatarIconListItem( - IconLeftWidget( - icon="plus" - ), - IconRightWidget( - icon="minus" - ), - text="Single-line item with avatar", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/OneLineAvatarIconListItem.png - :align: center - -.. TwoLineAvatarIconListItem: -TwoLineAvatarIconListItem -------------------------- - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: kv - - TwoLineAvatarIconListItem: - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - - IconLeftWidget: - icon: "plus" - - IconRightWidget: - icon: "minus" - - .. tab:: Declarative python style - - .. code-block:: python - - TwoLineAvatarIconListItem( - IconLeftWidget( - icon="plus" - ), - IconRightWidget( - icon="minus" - ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/TwoLineAvatarIconListItem.png - :align: center - -.. ThreeLineAvatarIconListItem: -ThreeLineAvatarIconListItem +List item with leading icon --------------------------- .. tabs:: - .. tab:: Declarative KV style + .. tab:: Declarative KV styles .. code-block:: kv - ThreeLineAvatarIconListItem: - text: "Three-line item with avatar" - secondary_text: "Secondary text here" - tertiary_text: "fit more text than usual" + MDListItem: - IconLeftWidget: - icon: "plus" + MDListItemLeadingIcon: + icon: "account" - IconRightWidget: - icon: "minus" + MDListItemHeadlineText: + text: "Headline" - .. tab:: Declarative python style + MDListItemSupportingText: + text: "Supporting text" + + MDListItemTertiaryText: + text: "Tertiary text" + + .. tab:: Declarative Python styles .. code-block:: python - ThreeLineAvatarIconListItem( - IconLeftWidget( - icon="plus" + MDListItem( + MDListItemLeadingIcon( + icon="account", ), - IconRightWidget( - icon="minus" + MDListItemHeadlineText( + text="Headline", + ), + MDListItemSupportingText( + text="Supporting text", + ), + MDListItemTertiaryText( + text="Tertiary text", ), - text="Single-line item with avatar", - secondary_text: "Secondary text here", - tertiary_text: "fit more text than usual", ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/ThreeLineAvatarIconListItem.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-leading-icon-list.png :align: center -Custom list item ----------------- +List item with trailing icon +---------------------------- .. tabs:: - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem - from kivymd.uix.selectioncontrol import MDCheckbox - from kivymd.icon_definitions import md_icons - - - KV = ''' - : - - IconLeftWidget: - icon: root.icon - - RightCheckbox: - - - MDScrollView: - - MDList: - id: scroll - ''' - - - class ListItemWithCheckbox(OneLineAvatarIconListItem): - '''Custom list item.''' - - icon = StringProperty("android") - - - class RightCheckbox(IRightBodyTouch, MDCheckbox): - '''Custom right container.''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - def on_start(self): - icons = list(md_icons.keys()) - for i in range(30): - self.root.ids.scroll.add_widget( - ListItemWithCheckbox(text=f"Item {i}", icon=icons[i]) - ) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem - from kivymd.uix.selectioncontrol import MDCheckbox - from kivymd.uix.scrollview import MDScrollView - from kivymd.uix.list import MDList - from kivymd.icon_definitions import md_icons - - - class RightCheckbox(IRightBodyTouch, MDCheckbox): - '''Custom right container.''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return ( - MDScrollView( - MDList( - id="scroll" - ) - ) - ) - - def on_start(self): - icons = list(md_icons.keys()) - for i in range(30): - self.root.ids.scroll.add_widget( - OneLineAvatarIconListItem( - IconLeftWidget( - icon=icons[i] - ), - RightCheckbox(), - text=f"Item {i}", - ) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-item.png - :align: center - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.list import IRightBodyTouch - - KV = ''' - OneLineAvatarIconListItem: - text: "One-line item with avatar" - on_size: - self.ids._right_container.width = container.width - self.ids._right_container.x = container.width - - IconLeftWidget: - icon: "cog" - - YourContainer: - id: container - - MDIconButton: - icon: "minus" - - MDIconButton: - icon: "plus" - ''' - - - class YourContainer(IRightBodyTouch, MDBoxLayout): - adaptive_width = True - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.list import IRightBodyTouch - from kivymd.uix.button import MDIconButton - from kivymd.uix.list import OneLineAvatarIconListItem, IconLeftWidget - - - class YourContainer(IRightBodyTouch, MDBoxLayout): - adaptive_width = True - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return ( - OneLineAvatarIconListItem( - IconLeftWidget( - icon="cog" - ), - YourContainer( - MDIconButton( - icon="minus" - ), - MDIconButton( - icon="plus" - ), - id="container" - ), - text="One-line item with avatar" - ) - ) - - def on_start(self): - container = self.root.ids.container - self.root.ids._right_container.width = container.width - container.x = container.width - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-list-right-container.png - :align: center - -Behavior --------- - -When using the `AvatarListItem` and `IconListItem` classes, when an icon is clicked, -the event of this icon is triggered: - -.. tabs:: - - .. tab:: Declarative KV style + .. tab:: Declarative KV styles .. code-block:: kv - OneLineIconListItem: - text: "Single-line item with icon" + MDListItem: - IconLeftWidget: - icon: "language-python" + MDListItemLeadingIcon: + icon: "account" - .. tab:: Declarative python style + MDListItemHeadlineText: + text: "Headline" + + MDListItemSupportingText: + text: "Supporting text" + + MDListItemTertiaryText: + text: "Tertiary text" + + MDListItemTrailingIcon: + icon: "trash-can-outline" + + .. tab:: Declarative Python styles .. code-block:: python - OneLineIconListItem( - IconLeftWidget( - icon="language-python" + MDListItem( + MDListItemLeadingIcon( + icon="account", + ), + MDListItemHeadlineText( + text="Headline", + ), + MDListItemSupportingText( + text="Supporting text", + ), + MDListItemTertiaryText( + text="Tertiary text", + ), + MDListItemTrailingIcon( + icon="trash-can-outline", ), - text="Single-line item with avatar", ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-trigger.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-leading-trailing-icon-list.png :align: center -You can disable the icon event using the `WithoutTouch` classes: +List item with trailing check +----------------------------- .. tabs:: - .. tab:: Declarative KV style + .. tab:: Declarative KV styles .. code-block:: kv - OneLineIconListItem: - text: "Single-line item with icon" + MDListItem: - IconLeftWidgetWithoutTouch: - icon: "language-python" + MDListItemLeadingIcon: + icon: "account" - .. tab:: Declarative python style + MDListItemHeadlineText: + text: "Headline" + + MDListItemSupportingText: + text: "Supporting text" + + MDListItemTertiaryText: + text: "Tertiary text" + + MDListItemTrailingCheckbox: + + .. tab:: Declarative Python styles .. code-block:: python - OneLineIconListItem( - IconLeftWidgetWithoutTouch( - icon="language-python" + MDListItem( + MDListItemLeadingIcon( + icon="account", + ), + MDListItemHeadlineText( + text="Headline", + ), + MDListItemSupportingText( + text="Supporting text", + ), + MDListItemTertiaryText( + text="Tertiary text", + ), + MDListItemTrailingCheckbox( ), - text="Single-line item with avatar", ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/list-icon-without-trigger.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/headline-supporting-tertiary-leading-trailing-check-list.png :align: center """ +from __future__ import annotations + __all__ = ( + "BaseListItemText", "BaseListItem", + "BaseListItemIcon", "MDList", - "ILeftBodyTouch", - "IRightBodyTouch", - "OneLineListItem", - "TwoLineListItem", - "ThreeLineListItem", - "OneLineAvatarListItem", - "TwoLineAvatarListItem", - "ThreeLineAvatarListItem", - "OneLineIconListItem", - "TwoLineIconListItem", - "ThreeLineIconListItem", - "OneLineRightIconListItem", - "TwoLineRightIconListItem", - "ThreeLineRightIconListItem", - "OneLineAvatarIconListItem", - "TwoLineAvatarIconListItem", - "ThreeLineAvatarIconListItem", - "ImageLeftWidget", - "ImageRightWidget", - "IconRightWidget", - "IconLeftWidget", - "CheckboxLeftWidget", - "IconLeftWidgetWithoutTouch", - "IconRightWidgetWithoutTouch", - "ImageRightWidgetWithoutTouch", - "ImageLeftWidgetWithoutTouch", + "MDListItem", + "MDListItemHeadlineText", + "MDListItemSupportingText", + "MDListItemTrailingSupportingText", + "MDListItemLeadingIcon", + "MDListItemTrailingIcon", + "MDListItemTrailingCheckbox", + "MDListItemLeadingAvatar", + "MDListItemTertiaryText", ) import os +from kivy import Logger +from kivy.clock import Clock from kivy.lang import Builder -from kivy.metrics import dp from kivy.properties import ( + NumericProperty, + ObjectProperty, BooleanProperty, ColorProperty, - ListProperty, - NumericProperty, - OptionProperty, - StringProperty, - VariableListProperty, ) from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.floatlayout import FloatLayout +from kivy.uix.boxlayout import BoxLayout -import kivymd.material_resources as m_res +from kivymd.uix.selectioncontrol import MDCheckbox from kivymd import uix_path from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( CircularRippleBehavior, DeclarativeBehavior, RectangularRippleBehavior, + BackgroundColorBehavior, ) -from kivymd.uix.button import MDIconButton +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior from kivymd.uix.fitimage import FitImage from kivymd.uix.gridlayout import MDGridLayout -from kivymd.uix.selectioncontrol import MDCheckbox +from kivymd.uix.label import MDLabel, MDIcon with open( os.path.join(uix_path, "list", "list.kv"), encoding="utf-8" @@ -979,14 +378,14 @@ with open( class MDList(MDGridLayout): """ - ListItem container. Best used in conjunction with a - :class:`kivy.uix.ScrollView`. + ListItem container. + Best used in conjunction with a :class:`kivy.uix.ScrollView`. When adding (or removing) a widget, it will resize itself to fit its children, plus top and bottom paddings as described by the `MD` spec. For more information, see in the - :class:`~kivymd.uix.gridlayout.MDGridLayout` classes documentation. + :class:`~kivymd.uix.gridlayout.MDGridLayout` class documentation. """ _list_vertical_padding = NumericProperty("8dp") @@ -998,638 +397,230 @@ class MDList(MDGridLayout): class BaseListItem( DeclarativeBehavior, - ThemableBehavior, + BackgroundColorBehavior, RectangularRippleBehavior, ButtonBehavior, - FloatLayout, + ThemableBehavior, + StateLayerBehavior, ): """ - Base class to all ListItems. Not supposed to be instantiated on its own. + Base class for list items. For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivy.uix.floatlayout.FloatLayout` classes documentation. + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.behaviors.state_layer_behavior.StateLayerBehavior` + classes documentation. """ - text = StringProperty() + divider = BooleanProperty(False) """ - Text shown in the first line. + Should I use divider for a list item. - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text_color = ColorProperty(None) - """ - Text color in (r, g, b, a) or string format used - if :attr:`~theme_text_color` is set to `'Custom'`. - - :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_style = StringProperty("Subtitle1") - """ - Text font style. - See `font-definitions `_ - for more information. - - :attr:`font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Subtitle1'`. - """ - - theme_text_color = StringProperty("Primary", allownone=True) - """ - The name of the color scheme for for the primary text. - - :attr:`theme_text_color` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Primary'`. - """ - - secondary_text = StringProperty() - """ - Text shown in the second line. - - :attr:`secondary_text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tertiary_text = StringProperty() - """ - The text is displayed on the third line. - - :attr:`tertiary_text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - secondary_text_color = ColorProperty(None) - """ - Text color in (r, g, b, a) or string format used for secondary text - if :attr:`~secondary_theme_text_color` is set to `'Custom'`. - - :attr:`secondary_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tertiary_text_color = ColorProperty(None) - """ - Text color in (r, g, b, a) or string format used for tertiary text - if :attr:`~tertiary_theme_text_color` is set to 'Custom'. - - :attr:`tertiary_text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - secondary_theme_text_color = StringProperty("Secondary", allownone=True) - """ - The name of the color scheme for for the secondary text. - - :attr:`secondary_theme_text_color` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Secondary'`. - """ - - tertiary_theme_text_color = StringProperty("Secondary", allownone=True) - """ - The name of the color scheme for for the tertiary text. - - :attr:`tertiary_theme_text_color` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Secondary'`. - """ - - secondary_font_style = StringProperty("Body1") - """ - Font style for secondary line. - See `font-definitions `_ - for more information. - - :attr:`secondary_font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - tertiary_font_style = StringProperty("Body1") - """ - Font style for tertiary line. - See `font-definitions `_ - for more information. - - :attr:`tertiary_font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'Body1'`. - """ - - divider = OptionProperty( - "Full", options=["Full", "Inset", None], allownone=True - ) - """ - Divider mode. Available options are: `'Full'`, `'Inset'` - and default to `'Full'`. - - :attr:`divider` is a :class:`~kivy.properties.OptionProperty` - and defaults to `'Full'`. + :attr:`divider` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. """ divider_color = ColorProperty(None) """ - Divider color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 + The divider color in (r, g, b, a) or string format. :attr:`divider_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - bg_color = ColorProperty(None) + md_bg_color_disabled = ColorProperty(None) """ - Background color for list item in (r, g, b, a) or string format. + The background color in (r, g, b, a) or string format of the list item when + the list item is disabled. - :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty` + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - radius = VariableListProperty([0], length=4) + +class BaseListItemText(MDLabel): """ - Canvas radius. + Base class for text labels of a list item. - .. code-block:: python - - # Top left corner slice. - MDBoxLayout: - md_bg_color: app.theme_cls.primary_color - radius: [25, 0, 0, 0] - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, 0, 0, 0]`. + For more information, see in the :class:`~kivymd.uix.label.label.MDLabel` + class documentation. """ - _txt_left_pad = NumericProperty("16dp") - _txt_top_pad = NumericProperty() - _txt_bot_pad = NumericProperty() - _txt_right_pad = NumericProperty(m_res.HORIZ_MARGINS) - _num_lines = 3 - _no_ripple_effect = BooleanProperty(False) - _touchable_widgets = ListProperty() - def on_touch_down(self, touch): - if self.propagate_touch_to_touchable_widgets(touch, "down"): - return - super().on_touch_down(touch) +class BaseListItemIcon(MDIcon): + """ + Base class for leading/trailing icon of list item. - def on_touch_move(self, touch, *args): - if self.propagate_touch_to_touchable_widgets(touch, "move", *args): - return - super().on_touch_move(touch, *args) + For more information, see in the :class:`~kivymd.uix.label.label.MDIcon` + class documentation. + """ - def on_touch_up(self, touch): - if self.propagate_touch_to_touchable_widgets(touch, "up"): - return - super().on_touch_up(touch) + icon_color = ColorProperty(None) + """ + Icon color in (r, g, b, a) or string format. - def propagate_touch_to_touchable_widgets(self, touch, touch_event, *args): - triggered = False - for i in self._touchable_widgets: - if i.collide_point(touch.x, touch.y): - triggered = True - if touch_event == "down": - i.on_touch_down(touch) - elif touch_event == "move": - i.on_touch_move(touch, *args) - elif touch_event == "up": - i.on_touch_up(touch) - return triggered + :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ - def add_widget(self, widget): - if issubclass(widget.__class__, ILeftBody): - self.ids._left_container.add_widget(widget) - elif issubclass(widget.__class__, ILeftBodyTouch): - self.ids._left_container.add_widget(widget) - self._touchable_widgets.append(widget) - elif issubclass(widget.__class__, IRightBody): - self.ids._right_container.add_widget(widget) - elif issubclass(widget.__class__, IRightBodyTouch): - self.ids._right_container.add_widget(widget) - self._touchable_widgets.append(widget) + icon_color_disabled = ColorProperty(None) + """ + The icon color in (r, g, b, a) or string format of the list item when + the list item is disabled. + + :attr:`icon_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + +class MDListItemHeadlineText(BaseListItemText): + """ + Implements a class for headline text of list item. + + For more information, see in the :class:`~BaseListItemText` + class documentation. + """ + + +class MDListItemSupportingText(BaseListItemText): + """ + Implements a class for secondary text of list item. + + For more information, see in the :class:`~BaseListItemText` + class documentation. + """ + + +class MDListItemTertiaryText(BaseListItemText): + """ + Implements a class for tertiary text of list item. + + For more information, see in the :class:`~BaseListItemText` + class documentation. + """ + + +class MDListItemTrailingSupportingText(BaseListItemText): + """ + Implements a class for trailing text of list item. + + For more information, see in the :class:`~BaseListItemText` + class documentation. + """ + + +class MDListItemLeadingIcon(BaseListItemIcon): + """ + Implements a class for leading icon class. + + For more information, see in the :class:`~BaseListItemIcon` + class documentation. + """ + + +class MDListItemLeadingAvatar( + ThemableBehavior, CircularRippleBehavior, ButtonBehavior, FitImage +): + """ + Implements a class for leading avatar class. + + For more information, see in the + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.fitimage.fitimage.FitImage` + classes documentation. + """ + + _list_item = ObjectProperty() + + +class MDListItemTrailingIcon(BaseListItemIcon): + """ + Implements a class for trailing icon class. + + For more information, see in the :class:`~BaseListItemIcon` + class documentation. + """ + + +class MDListItemTrailingCheckbox(MDCheckbox): + """ + Implements a class for trailing checkbox class. + + For more information, see in the + :class:`~kivymd.uix.selectioncontrol.selectioncontrol.MDCheckbox` + class documentation. + """ + + +class MDListItem(BaseListItem, BoxLayout): + """ + Implements a list item. + + For more information, see in the + :class:`~BaseListItem` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + """ + + def add_widget(self, widget, *args, **kwargs): + if isinstance( + widget, + ( + MDListItemHeadlineText, + MDListItemSupportingText, + MDListItemTertiaryText, + ), + ): + if len(self.ids.text_container.children) < 3: + self.ids.text_container.add_widget(widget) + elif len(self.ids.text_container.children) > 3: + self._set_warnings(widget) + elif isinstance( + widget, (MDListItemLeadingIcon, MDListItemLeadingAvatar) + ): + if not self.ids.leading_container.children: + widget._list_item = self + self.ids.leading_container.add_widget(widget) + Clock.schedule_once( + lambda x: self._set_with_container( + self.ids.leading_container, widget + ) + ) + else: + self._set_warnings(widget) + elif isinstance( + widget, + ( + MDListItemTrailingIcon, + MDListItemTrailingCheckbox, + MDListItemTrailingSupportingText, + ), + ): + if not self.ids.trailing_container.children: + self.ids.trailing_container.add_widget(widget) + Clock.schedule_once( + lambda x: self._set_with_container( + self.ids.trailing_container, widget + ) + ) + else: + self._set_warnings(widget) else: return super().add_widget(widget) - def remove_widget(self, widget): - super().remove_widget(widget) - if widget in self._touchable_widgets: - self._touchable_widgets.remove(widget) - - -class ILeftBody: - """ - Pseudo-interface for widgets that go in the left container for - ListItems that support it. - - Implements nothing and requires no implementation, for annotation only. - """ - - -class ILeftBodyTouch: - """ - Same as :class:`~ILeftBody`, but allows the widget to receive touch - events instead of triggering the ListItem's ripple effect. - """ - - -class IRightBody: - """ - Pseudo-interface for widgets that go in the right container for - ListItems that support it. - - Implements nothing and requires no implementation, for annotation only. - """ - - -class IRightBodyTouch: - """ - Same as :class:`~IRightBody`, but allows the widget to receive touch - events instead of triggering the ``ListItem``'s ripple effect - """ - - -class OneLineListItem(BaseListItem): - """ - A one line list item. - - For more information, see in the :class:`~BaseListItem` - classes documentation. - """ - - _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") - _height = NumericProperty() - _num_lines = 1 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.height = dp(48) if not self._height else self._height - - -class TwoLineListItem(BaseListItem): - """ - A two line list item. - - For more information, see in the :class:`~BaseListItem` - classes documentation. - """ - - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") - _height = NumericProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineListItem(BaseListItem): - """ - A three line list item. - - For more information, see in the :class:`~BaseListItem` - classes documentation. - """ - - _txt_top_pad = NumericProperty("16dp") - _txt_bot_pad = NumericProperty("15dp") - _height = NumericProperty() - _num_lines = 3 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.height = dp(88) if not self._height else self._height - - -class OneLineAvatarListItem(BaseListItem): - """ - A one line list item with left image. - - For more information, see in the :class:`~BaseListItem` - classes documentation. - """ - - _txt_left_pad = NumericProperty("72dp") - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("19dp") - _height = NumericProperty() - _num_lines = 1 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.height = dp(56) if not self._height else self._height - - -class TwoLineAvatarListItem(OneLineAvatarListItem): - """ - A two line list item with left image. - - For more information, see in the :class:`~OneLineAvatarListItem` - classes documentation. - """ - - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") - _height = NumericProperty() - _num_lines = 2 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineAvatarListItem(ThreeLineListItem): - """ - A three line list item with left image. - - For more information, see in the :class:`~ThreeLineListItem` - classes documentation. - """ - - _txt_left_pad = NumericProperty("72dp") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - -class OneLineIconListItem(OneLineListItem): - """ - A one line list item with left icon. - - For more information, see in the :class:`~OneLineListItem` - classes documentation. - """ - - _txt_left_pad = NumericProperty("72dp") - - -class TwoLineIconListItem(OneLineIconListItem): - """ - A two line list item with left icon. - - For more information, see in the :class:`~OneLineIconListItem` - classes documentation. - """ - - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") - _height = NumericProperty() - _num_lines = 2 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineIconListItem(ThreeLineListItem): - """ - A three line list item with left icon. - - For more information, see in the :class:`~ThreeLineListItem` - classes documentation. - """ - - _txt_left_pad = NumericProperty("72dp") - - -class OneLineRightIconListItem(OneLineListItem): - """ - A one line list item with right icon/image. - - For more information, see in the :class:`~OneLineListItem` - classes documentation. - """ - - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class TwoLineRightIconListItem(OneLineRightIconListItem): - """ - A two line list item with right icon/image. - - For more information, see in the :class:`~OneLineRightIconListItem` - classes documentation. - """ - - _txt_top_pad = NumericProperty("20dp") - _txt_bot_pad = NumericProperty("15dp") - _height = NumericProperty() - _num_lines = 2 - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.height = dp(72) if not self._height else self._height - - -class ThreeLineRightIconListItem(ThreeLineListItem): - """ - A three line list item with right icon/image. - - For more information, see in the :class:`~ThreeLineRightIconListItem` - classes documentation. - """ - - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class OneLineAvatarIconListItem(OneLineAvatarListItem): - """ - A one line list item with left/right icon/image/widget. - - For more information, see in the :class:`~OneLineAvatarListItem` - classes documentation. - """ - - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class TwoLineAvatarIconListItem(TwoLineAvatarListItem): - """ - A two line list item with left/right icon/image/widget. - - For more information, see in the :class:`~TwoLineAvatarListItem` - classes documentation. - """ - - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class ThreeLineAvatarIconListItem(ThreeLineAvatarListItem): - """ - A three line list item with left/right icon/image/widget. - - For more information, see in the :class:`~ThreeLineAvatarListItem` - classes documentation. - """ - - _txt_right_pad = NumericProperty("40dp") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._txt_right_pad = dp(40) + m_res.HORIZ_MARGINS - - -class TouchBehavior: - def on_release(self): - if issubclass(self.parent.parent.__class__, BaseListItem): - self.parent.parent.dispatch("on_release") - - -class ImageLeftWidget( - CircularRippleBehavior, ButtonBehavior, ILeftBodyTouch, FitImage -): - """ - The widget implements the left image for use in ListItem classes. - - For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~ILeftBodyTouch` and - :class:`~kivymd.uix.fitimage.FitImage` classes documentation. - """ - - -class ImageLeftWidgetWithoutTouch( - CircularRippleBehavior, TouchBehavior, ButtonBehavior, ILeftBody, FitImage -): - """ - Disables the image event. - The widget implements the left image for use in `ListItem` classes. - - For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~TouchBehavior` and - :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~ILeftBody` and - :class:`~kivymd.uix.fitimage.FitImage` classes documentation. - - .. versionadded:: 1.0.0 - """ - - _no_ripple_effect = True - - -class ImageRightWidget( - CircularRippleBehavior, ButtonBehavior, IRightBodyTouch, FitImage -): - """ - The widget implements the right image for use in ListItem classes. - - For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~IRightBodyTouch` and - :class:`~kivymd.uix.fitimage.FitImage` classes documentation. - """ - - -class ImageRightWidgetWithoutTouch( - CircularRippleBehavior, TouchBehavior, ButtonBehavior, IRightBody, FitImage -): - """ - Disables the image event. - The widget implements the right image for use in `ListItem` classes. - - For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and - :class:`~TouchBehavior` and - :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~IRightBody` and - :class:`~kivymd.uix.fitimage.FitImage` classes documentation. - - .. versionadded:: 1.0.0 - """ - - _no_ripple_effect = True - - -class IconRightWidget(IRightBodyTouch, MDIconButton): - """ - The widget implements the right icon for use in ListItem classes. - - For more information, see in the - :class:`~IRightBodyTouch` and - :class:`~kivymd.uix.button.MDIconButton` - classes documentation. - """ - - pos_hint = {"center_y": 0.5} - - -class IconRightWidgetWithoutTouch(TouchBehavior, IRightBody, MDIconButton): - """ - Disables the icon event. - The widget implements the right icon for use in ListItem classes. - - For more information, see in the - :class:`~TouchBehavior` and - :class:`~IRightBody` and - :class:`~kivymd.uix.button.MDIconButton` - classes documentation. - - .. versionadded:: 1.0.0 - """ - - pos_hint = {"center_y": 0.5} - _no_ripple_effect = True - - -class IconLeftWidget(ILeftBodyTouch, MDIconButton): - """ - The widget implements the left icon for use in ListItem classes. - - For more information, see in the - :class:`~ILeftBodyTouch` and - :class:`~kivymd.uix.button.MDIconButton` - classes documentation. - """ - - pos_hint = {"center_y": 0.5} - - -class IconLeftWidgetWithoutTouch(TouchBehavior, ILeftBody, MDIconButton): - """ - Disables the icon event. - The widget implements the left icon for use in ListItem classes. - - For more information, see in the - :class:`~TouchBehavior` and - :class:`~ILeftBody` and - :class:`~kivymd.uix.button.MDIconButton` - classes documentation. - - .. versionadded:: 1.0.0 - """ - - pos_hint = {"center_y": 0.5} - _no_ripple_effect = True - - -class CheckboxLeftWidget(ILeftBodyTouch, MDCheckbox): - """ - The widget implements the left checkbox element for use in ListItem classes. - - For more information, see in the - :class:`~ILeftBodyTouch` and - :class:`~kivymd.uix.selectioncontrol.MDCheckbox` - classes documentation. - """ + def _set_warnings(self, widget): + Logger.warning( + f"KivyMD: " + f"Do not use more than one <{widget.__class__.__name__}> " + f"widget. This is contrary to the material design rules " + f"of version 3" + ) + + def _set_with_container(self, container, widget): + container.width = widget.width diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/__init__.cpython-311.pyc index 9b80d34..5ea72d7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/menu.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/menu.cpython-311.pyc index db07c88..b96e3aa 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/menu.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/__pycache__/menu.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.kv index a53c071..8a52ba4 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.kv @@ -22,7 +22,6 @@ MDLabel: text: root.text pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" size_hint_x: None @@ -34,25 +33,26 @@ + container.padding[2] \ + container.spacing \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color MDTrailingTextContainer: id: trailing_container text: root.trailing_text adaptive_width: True - theme_text_color: "Custom" if root.trailing_text_color else "Primary" + theme_text_color: "Custom" text_color: - root.trailing_text_color \ - if root.trailing_text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.trailing_text_color else \ + root.trailing_text_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -74,16 +74,15 @@ size_hint: None, None size: "48dp", "48dp" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.leading_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.leading_icon_color \ - if root.leading_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.leading_icon_color else \ + root.leading_icon_color MDLabel: text: root.text pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" size_hint_x: None @@ -97,10 +96,11 @@ + container.spacing \ + dp(18) \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color Widget: @@ -108,16 +108,16 @@ id: trailing_container text: root.trailing_text adaptive_width: True - theme_text_color: "Custom" if root.trailing_text_color else "Primary" + theme_text_color: "Custom" text_color: - root.trailing_text_color \ - if root.trailing_text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.trailing_text_color else \ + root.trailing_text_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -140,7 +140,6 @@ size_hint_x: None shorten_from: "right" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" width: @@ -152,10 +151,11 @@ + container.spacing \ + dp(18) \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color Widget: @@ -165,16 +165,16 @@ size: "48dp", "48dp" pos_hint: {"center_y": .5} icon: root.trailing_icon - theme_text_color: "Custom" if root.trailing_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.trailing_icon_color \ - if root.trailing_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.trailing_icon_color else \ + root.trailing_icon_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -190,21 +190,21 @@ size_hint: None, None size: "48dp", "48dp" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.trailing_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.trailing_icon_color \ - if root.trailing_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.trailing_icon_color else \ + root.trailing_icon_color MDLabel: text: root.trailing_text adaptive_size: True pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.trailing_text_color else "Primary" + theme_text_color: "Custom" text_color: - root.trailing_text_color \ - if root.trailing_text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color @@ -222,7 +222,6 @@ size_hint_x: None shorten_from: "right" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" width: @@ -233,10 +232,11 @@ + container.padding[2] \ + container.spacing \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color MDTrailingIconTextContainer: id: trailing_container @@ -245,10 +245,10 @@ trailing_text_color: root.trailing_text_color trailing_icon_color: root.trailing_icon_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -263,18 +263,18 @@ text: root.text valign: "center" padding_x: "12dp" - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -296,16 +296,15 @@ size_hint: None, None size: "48dp", "48dp" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.leading_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.leading_icon_color \ - if root.leading_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.leading_icon_color else \ + root.leading_icon_color MDLabel: text: root.text pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" size_hint_x: None @@ -319,10 +318,11 @@ + container.spacing \ + dp(18) \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color Widget: @@ -333,10 +333,10 @@ trailing_icon_color: root.trailing_icon_color trailing_text_color: root.trailing_text_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -358,11 +358,11 @@ size_hint: None, None size: "48dp", "48dp" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.leading_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.leading_icon_color \ - if root.leading_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.leading_icon_color else \ + root.leading_icon_color MDLabel: id: label @@ -371,7 +371,6 @@ size_hint_x: None shorten_from: "right" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" width: @@ -384,10 +383,11 @@ + container.spacing \ + dp(18) \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color Widget: @@ -397,16 +397,16 @@ size: "48dp", "48dp" pos_hint: {"center_y": .5} icon: root.trailing_icon - theme_text_color: "Custom" if root.trailing_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.trailing_icon_color \ - if root.trailing_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.trailing_icon_color else \ + root.trailing_icon_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -428,11 +428,11 @@ size_hint: None, None size: "48dp", "48dp" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.leading_icon_color else "Primary" + theme_text_color: "Custom" text_color: - root.leading_icon_color \ - if root.leading_icon_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.leading_icon_color else \ + root.leading_icon_color MDLabel: id: label @@ -441,7 +441,6 @@ size_hint_x: None shorten_from: "right" pos_hint: {"center_y": .5} - theme_text_color: "Custom" if root.text_color else "Primary" shorten: True shorten_from: "right" width: @@ -452,15 +451,16 @@ + container.padding[2] \ + container.spacing \ ) + theme_text_color: "Custom" text_color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + app.theme_cls.onSurfaceVariantColor \ + if not root.text_color else \ + root.text_color - MDSeparator: + MDDivider: md_bg_color: ( \ - self.theme_cls.divider_color \ + app.theme_cls.outlineVariantColor \ if not root.divider_color \ else root.divider_color \ ) \ @@ -470,14 +470,18 @@ orientation: "vertical" - elevation: root.elevation - shadow_radius: root.shadow_radius - shadow_softness: root.shadow_softness - shadow_offset: root.shadow_offset - shadow_color: root.shadow_color - shadow_color: root.shadow_color - radius: root.radius size_hint: None, None + focus_behavior: False + style: "elevated" + elevation_level: 2 + shadow_softness: 1.5 + shadow_radius: 4 + theme_bg_color: root.theme_bg_color +# md_bg_color: +# app.theme_cls.surfaceContainerColor \ +# if root.theme_bg_color == "Primary" else \ +# root.md_bg_color + MDBoxLayout: id: content_header diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.py index 012eef4..eabdc9c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/menu/menu.py @@ -205,6 +205,7 @@ You can use the following parameters to customize the menu items: :align: center .. Header: + Header ------ @@ -338,6 +339,7 @@ for both the righthand and lefthand menus. :align: center .. Position: + Position ======== @@ -405,50 +407,49 @@ Center position .. code-block:: python from kivy.lang import Builder - from kivy.metrics import dp - from kivymd.app import MDApp from kivymd.uix.menu import MDDropdownMenu + from kivymd.app import MDApp KV = ''' - MDScreen: + MDScreen + md_bg_color: self.theme_cls.backgroundColor MDDropDownItem: - id: drop_item - pos_hint: {'center_x': .5, 'center_y': .5} - text: 'Item 0' - on_release: app.menu.open() + pos_hint: {"center_x": .5, "center_y": .5} + on_release: app.open_drop_item_menu(self) + + MDDropDownItemText: + id: drop_text + text: "Item" ''' - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) + class Example(MDApp, CommonApp): + drop_item_menu: MDDropdownMenu = None + + def open_drop_item_menu(self, item): menu_items = [ { - "text": f"Item {i}", - "on_release": lambda x=f"Item {i}": self.set_item(x), + "text": f"{i}", + "on_release": lambda x=f"Item {i}": self.menu_callback(x), } for i in range(5) ] - self.menu = MDDropdownMenu( - caller=self.screen.ids.drop_item, - items=menu_items, - position="center", - ) - self.menu.bind() + if not self.drop_item_menu: + self.drop_item_menu = MDDropdownMenu( + caller=item, items=menu_items, position="center" + ) + self.drop_item_menu.open() - def set_item(self, text_item): - self.screen.ids.drop_item.set_item(text_item) - self.menu.dismiss() + def menu_callback(self, text_item): + self.root.ids.drop_text.text = text_item + self.drop_item_menu.dismiss() def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" - return self.screen + return Builder.load_string(KV) - Test().run() + Example().run() .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/menu-position-center.gif :align: center @@ -636,14 +637,14 @@ from kivy.properties import ( ) from kivy.uix.recycleview import RecycleView -import kivymd.material_resources as m_res from kivymd import uix_path from kivymd.uix.behaviors import StencilBehavior, RectangularRippleBehavior from kivymd.uix.behaviors.motion_behavior import MotionDropDownMenuBehavior from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.card import MDCard from kivymd.uix.label import MDLabel -from kivymd.uix.list import IRightBody + +# from kivymd.uix.list import IRightBody with open( os.path.join(uix_path, "menu", "menu.kv"), encoding="utf-8" @@ -672,7 +673,7 @@ class BaseDropdownItem(RectangularRippleBehavior, ButtonBehavior, MDBoxLayout): .. versionadded:: 1.2.0 For more information, see in the - :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and :class:`~kivymd.uix.boxlayout.MDBoxLayout` classes. """ @@ -762,7 +763,7 @@ class BaseDropdownItem(RectangularRippleBehavior, ButtonBehavior, MDBoxLayout): """ -class MDTrailingTextContainer(BaseDropdownItem, IRightBody, MDLabel): +class MDTrailingTextContainer(BaseDropdownItem, MDLabel): """ Implements a container for trailing text. @@ -775,7 +776,7 @@ class MDTrailingTextContainer(BaseDropdownItem, IRightBody, MDLabel): """ -class MDTrailingIconTextContainer(BaseDropdownItem, IRightBody, MDBoxLayout): +class MDTrailingIconTextContainer(BaseDropdownItem, MDBoxLayout): """ Implements a container for trailing icons and trailing text. @@ -877,9 +878,9 @@ class MDDropdownMenu(MotionDropDownMenuBehavior, StencilBehavior, MDCard): Dropdown menu class. For more information, see in the - :class:`~kivymd.uix.behaviors.MotionDropDownMenuBehavior` and - :class:`~kivymd.uix.behaviors.StencilBehavior` and - :class:`~kivymd.uix.card.MDCard` + :class:`~kivymd.uix.behaviors.motion_behavior.MotionDropDownMenuBehavior` and + :class:`~kivymd.uix.behaviors.stencil_behavior.StencilBehavior` and + :class:`~kivymd.uix.card.card.MDCard` classes documentation. :Events: @@ -1058,42 +1059,6 @@ class MDDropdownMenu(MotionDropDownMenuBehavior, StencilBehavior, MDCard): and defaults to `'[dp(7)]'`. """ - elevation = NumericProperty(m_res.DROP_DOWN_MENU_ELEVATION) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.elevation` - attribute. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - shadow_radius = VariableListProperty([6], length=4) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_radius` - attribute. - - :attr:`shadow_radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[6]`. - """ - - shadow_softness = NumericProperty(m_res.DROP_DOWN_MENU_SOFTNESS) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_softness` - attribute. - - :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` - and defaults to `6`. - """ - - shadow_offset = ListProperty(m_res.DROP_DOWN_MENU_OFFSET) - """ - See :attr:`kivymd.uix.behaviors.elevation.CommonElevationBehavior.shadow_offset` - attribute. - - :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` - and defaults to `(0, -2)`. - """ - _items = [] _start_coords = [] _tar_x = 0 @@ -1381,6 +1346,9 @@ class MDDropdownMenu(MotionDropDownMenuBehavior, StencilBehavior, MDCard): items.append(data) self._items = items + # Update items in view + if hasattr(self, "menu"): + self.menu.data = self._items def on_header_cls( self, instance_dropdown_menu, instance_user_menu_header @@ -1424,13 +1392,13 @@ if __name__ == "__main__": from kivy.metrics import dp from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton + from kivymd.uix.button import MDButton, MDButtonText from kivymd.uix.screen import MDScreen class Test(MDApp): def __init__(self, **kwargs): super().__init__(**kwargs) - self.screen = MDScreen() + self.screen = MDScreen(md_bg_color=self.theme_cls.backgroundColor) menu_items = [{"text": f"Item {i}"} for i in range(55)] self.menu = MDDropdownMenu(items=menu_items, width_mult=4) @@ -1452,7 +1420,13 @@ if __name__ == "__main__": ] for pos_hint in pos_hints: self.screen.add_widget( - MDRaisedButton(pos_hint=pos_hint, on_release=self.open_menu) + MDButton( + MDButtonText( + text="Press me", + ), + pos_hint=pos_hint, + on_release=self.open_menu, + ) ) def build(self): diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__init__.py new file mode 100644 index 0000000..a492e02 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__init__.py @@ -0,0 +1,7 @@ +# NOQA F401 +from .navigationbar import ( + MDNavigationBar, + MDNavigationItem, + MDNavigationItemLabel, + MDNavigationItemIcon, +) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..a19eb78 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__pycache__/navigationbar.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__pycache__/navigationbar.cpython-311.pyc new file mode 100644 index 0000000..b7af579 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/__pycache__/navigationbar.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/navigationbar.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/navigationbar.kv new file mode 100644 index 0000000..8695e3d --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/navigationbar.kv @@ -0,0 +1,133 @@ + + size_hint_y: None + height: "80dp" + elevation_level: 0 + md_bg_color: + self.theme_cls.surfaceContainerColor \ + if root.theme_bg_color == "Primary" else \ + root.md_bg_color + + + + size_hint: None, None + size: "24sp", "24sp" + # theme_text_color: "Custom" + icon_color: + ( \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.active else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + if self.theme_icon_color == "Primary" else \ + ( \ + self.icon_color_active if self.icon_color_active else \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.active else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + ) \ + if self.parent.parent.active else \ + ( \ + self.icon_color_normal if self.icon_color_normal else \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.active else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + ) \ + ) \ + if self.parent else self.theme_cls.transparentColor + on_icon: + if self.icon not in md_icons.keys(): \ + self.size_hint = (None, None); \ + self.width = self.font_size; \ + self.height = self.font_size + + canvas.before: + Color: + rgba: + ( \ + ( \ + self.theme_cls.secondaryContainerColor \ + if not self.parent.parent.indicator_color else \ + self.parent.parent.indicator_color \ + ) \ + if self.parent.parent.active else \ + self.theme_cls.transparentColor \ + ) \ + if self.parent else self.theme_cls.transparentColor + RoundedRectangle: + radius: [dp(16), ] + size: + ( \ + (self.parent.parent._selected_region_width, dp(32)) \ + ) \ + if self.parent else (0, dp(32)) + pos: + ( \ + (self.center_x - self.parent.parent._selected_region_width / 2, \ + self.center_y - dp(16)) \ + ) \ + if self.parent else (0, 0) + + + + adaptive_size: True + role: "medium" + text_color: + ( \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.active else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + if self.theme_text_color == "Primary" else \ + ( \ + self.text_color_active if self.text_color_active else \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.active else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + ) \ + if self.parent.parent.active else \ + ( \ + self.text_color_normal if self.text_color_normal else \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.active else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + ) \ + ) \ + if self.parent else self.theme_cls.transparentColor + + + + + MDNavigationItemIconContainer: + id: icon_container + size_hint: None, None + size: self.minimum_size + pos_hint: {"center_x": .5} + y: + ( \ + (root.parent.height - (self.height + dp(16))) \ + if len(label_container.children) else \ + (root.parent.height / 2 - self.height / 2) \ + ) \ + if root.parent else 0 + + MDNavigationItemLabelContainer: + id: label_container + size_hint: None, None + size: self.minimum_size + pos_hint: {"center_x": .5} + y: + "16dp" \ + if len(icon_container.children) else \ + ( \ + (root.parent.height / 2 - self.height / 2) if root.parent else 0 \ + ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/navigationbar.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/navigationbar.py new file mode 100644 index 0000000..e2f45cc --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationbar/navigationbar.py @@ -0,0 +1,624 @@ +""" +Components/Navigation bar +========================= + +.. seealso:: + + `Material Design 3 spec, Navigation bar `_ + +.. rubric:: Bottom navigation bars allow movement between primary destinations in an app: + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-navigation.png + :align: center + +Usage +----- + +.. code-block:: kv + + + + MDNavigationBar: + + MDNavigationItem: + + MDNavigationItemIcon: + + MDNavigationItemLabel: + + [...] + +Anatomy +======= + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigationbar-item-anatomy.png + :align: center + +Example +------- + +.. tabs:: + + .. tab:: Declarative KV style + + .. code-block:: python + + from kivy.lang import Builder + from kivy.properties import StringProperty + + from kivymd.app import MDApp + from kivymd.uix.navigationbar import MDNavigationBar, MDNavigationItem + from kivymd.uix.screen import MDScreen + + + class BaseMDNavigationItem(MDNavigationItem): + icon = StringProperty() + text = StringProperty() + + + class BaseScreen(MDScreen): + image_size = StringProperty() + + + KV = ''' + + + MDNavigationItemIcon: + icon: root.icon + + MDNavigationItemLabel: + text: root.text + + + + + FitImage: + source: f"https://picsum.photos/{root.image_size}/{root.image_size}" + size_hint: .9, .9 + pos_hint: {"center_x": .5, "center_y": .5} + radius: dp(24) + + + MDBoxLayout: + orientation: "vertical" + md_bg_color: self.theme_cls.backgroundColor + + MDScreenManager: + id: screen_manager + + BaseScreen: + name: "Screen 1" + image_size: "1024" + + BaseScreen: + name: "Screen 2" + image_size: "800" + + BaseScreen: + name: "Screen 3" + image_size: "600" + + + MDNavigationBar: + on_switch_tabs: app.on_switch_tabs(*args) + + BaseMDNavigationItem + icon: "gmail" + text: "Screen 1" + active: True + + BaseMDNavigationItem + icon: "twitter" + text: "Screen 2" + + BaseMDNavigationItem + icon: "linkedin" + text: "Screen 3" + ''' + + + class Example(MDApp): + def on_switch_tabs( + self, + bar: MDNavigationBar, + item: MDNavigationItem, + item_icon: str, + item_text: str, + ): + self.root.ids.screen_manager.current = item_text + + def build(self): + return Builder.load_string(KV) + + + Example().run() + + .. tab:: Declarative python style + + .. code-block:: python + + from kivy.metrics import dp + from kivy.properties import StringProperty + + from kivymd.uix.fitimage import FitImage + from kivymd.uix.screen import MDScreen + from kivymd.uix.screenmanager import MDScreenManager + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.navigationbar import ( + MDNavigationBar, + MDNavigationItem, + MDNavigationItemLabel, + MDNavigationItemIcon, + ) + from kivymd.app import MDApp + + + class BaseMDNavigationItem(MDNavigationItem): + icon = StringProperty() + text = StringProperty() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_widget(MDNavigationItemIcon(icon=self.icon)) + self.add_widget(MDNavigationItemLabel(text=self.text)) + + + class BaseScreen(MDScreen): + image_size = StringProperty() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_widget( + FitImage( + source=f"https://picsum.photos/{self.image_size}/{self.image_size}", + size_hint=(0.9, 0.9), + pos_hint={"center_x": 0.5, "center_y": 0.5}, + radius=dp(24), + ), + ) + + + class Example(MDApp): + def on_switch_tabs( + self, + bar: MDNavigationBar, + item: MDNavigationItem, + item_icon: str, + item_text: str, + ): + self.root.get_ids().screen_manager.current = item_text + + def build(self): + return MDBoxLayout( + MDScreenManager( + BaseScreen( + name="Screen 1", + image_size="1024", + ), + BaseScreen( + name="Screen 2", + image_size="800", + ), + BaseScreen( + name="Screen 3", + image_size="600", + ), + id="screen_manager", + ), + MDNavigationBar( + BaseMDNavigationItem( + icon="gmail", + text="Screen 1", + active=True, + ), + BaseMDNavigationItem( + icon="twitter", + text="Screen 2", + ), + BaseMDNavigationItem( + icon="linkedin", + text="Screen 3", + ), + on_switch_tabs=self.on_switch_tabs, + ), + orientation="vertical", + md_bg_color=self.theme_cls.backgroundColor, + ) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigationbar-usage.gif + :align: center + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + + class Example(MDApp): + def build(self): + return Builder.load_string( + ''' + MDScreen: + + MDBottomNavigation: + + MDBottomNavigationItem: + name: 'screen 1' + text: 'Mail' + icon: 'gmail' + badge_icon: "numeric-10" + + MDLabel: + text: 'Screen 1' + halign: 'center' + + MDBottomNavigationItem: + name: 'screen 2' + text: 'Twitter' + icon: 'twitter' + + MDLabel: + text: 'Screen 2' + halign: 'center' + ''' + ) + + + Example().run() + +2.0.0 version +------------- + +MDNavigationBar in version 2.0.0 no longer provides a screen manager for +content placement. You have to implement it yourself. This is due to the fact +that when using MDNavigationBar and MDTabs widgets at the same time, there +were conflicts between their screen managers. + +.. code-block:: python + + from kivy.lang import Builder + from kivy.properties import StringProperty + + from kivymd.app import MDApp + from kivymd.uix.navigationbar import MDNavigationBar, MDNavigationItem + from kivymd.uix.screen import MDScreen + + + class BaseMDNavigationItem(MDNavigationItem): + icon = StringProperty() + text = StringProperty() + + + class BaseScreen(MDScreen): + ... + + + KV = ''' + + + MDNavigationItemIcon: + icon: root.icon + + MDNavigationItemLabel: + text: root.text + + + + + MDLabel: + text: root.name + halign: "center" + + + MDBoxLayout: + orientation: "vertical" + md_bg_color: self.theme_cls.backgroundColor + + MDScreenManager: + id: screen_manager + + BaseScreen: + name: "Screen 1" + + BaseScreen: + name: "Screen 2" + + + MDNavigationBar: + on_switch_tabs: app.on_switch_tabs(*args) + + BaseMDNavigationItem + icon: "gmail" + text: "Screen 1" + active: True + + BaseMDNavigationItem + icon: "twitter" + text: "Screen 2" + ''' + + + class Example(MDApp): + def on_switch_tabs( + self, + bar: MDNavigationBar, + item: MDNavigationItem, + item_icon: str, + item_text: str, + ): + self.root.ids.screen_manager.current = item_text + + def build(self): + return Builder.load_string(KV) + + + Example().run() +""" + +from __future__ import annotations + +__all__ = ( + "MDNavigationItem", + "MDNavigationBar", + "MDNavigationItemLabel", + "MDNavigationItemIcon", +) + +import os + +from kivy.animation import Animation +from kivy.clock import Clock +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import ( + BooleanProperty, + ColorProperty, + NumericProperty, + StringProperty, +) +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.relativelayout import RelativeLayout + +from kivymd.uix.label import MDLabel, MDIcon +from kivymd.uix.boxlayout import MDBoxLayout +from kivymd import uix_path +from kivymd.uix.behaviors import ( + DeclarativeBehavior, + CommonElevationBehavior, + RectangularRippleBehavior, +) +from kivymd.utils.set_bars_colors import set_bars_colors + +with open( + os.path.join(uix_path, "navigationbar", "navigationbar.kv"), + encoding="utf-8", +) as kv_file: + Builder.load_string(kv_file.read()) + + +class MDNavigationItemLabel(MDLabel): + """ + Implements a text label for the :class:`~MDNavigationItem` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + text_color_active = ColorProperty(None) + """ + Item icon color in (r, g, b, a) or string format. + + :attr:`text_color_active` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + text_color_normal = ColorProperty(None) + """ + Item icon color in (r, g, b, a) or string format. + + :attr:`text_color_normal` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + +class MDNavigationItemIcon(MDIcon): + """ + Implements a icon for the :class:`~MDNavigationItem` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDIcon` class documentation. + """ + + icon_color_active = ColorProperty(None) + """ + Item icon color in (r, g, b, a) or string format. + + :attr:`icon_color_active` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + icon_color_normal = ColorProperty(None) + """ + Item icon color in (r, g, b, a) or string format. + + :attr:`icon_color_normal` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + +class MDNavigationItem( + DeclarativeBehavior, + RectangularRippleBehavior, + ButtonBehavior, + RelativeLayout, +): + """ + Bottom item class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivy.uix.anchorlayout.AnchorLayout` and + :class:`~kivy.uix.behaviors.ButtonBehavior` + classes documentation. + + .. versionchanged:: 2.0.0 + Rename class from `MDBottomNavigationItem` to `MDNavigationItem`. + """ + + active = BooleanProperty(False) + """ + Indicates if the bar item is active or inactive. + + :attr:`active` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + indicator_color = ColorProperty(None) + """ + The background color in (r, g, b, a) or string format of the highlighted + item. + + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + Rename property from `selected_color_background` to `indicator_color`. + + :attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + indicator_transition = StringProperty("in_out_sine") + """ + Animation type of the active element indicator. + + :attr:`indicator_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'in_out_sine'`. + """ + + indicator_duration = NumericProperty(0.1) + """ + Duration of animation of the active element indicator. + + :attr:`indicator_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.1`. + """ + + _selected_region_width = NumericProperty(dp(0)) + + def on_active(self, instance, value) -> None: + """Fired when the values of :attr:`active` change.""" + + def on_active(*args): + Animation( + _selected_region_width=dp(64) if value else 0, + t=self.indicator_transition, + d=self.indicator_duration, + ).start(self) + + Clock.schedule_once(on_active) + + def on_release(self) -> None: + """Fired when clicking on a panel item.""" + + self.parent.set_active_item(self) + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDNavigationItemLabel): + self.ids.label_container.add_widget(widget) + elif isinstance(widget, MDNavigationItemIcon): + self.ids.icon_container.add_widget(widget) + elif isinstance( + widget, + (MDNavigationItemIconContainer, MDNavigationItemLabelContainer), + ): + return super().add_widget(widget) + + +class MDNavigationBar(CommonElevationBehavior, MDBoxLayout): + """ + A navigation bar class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.boxlayout.MDBoxLayout` + classes documentation. + + :Events: + :attr:`on_switch_tabs` + Fired when switching tabs. + + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + Rename class from `MDBottomNavigation` to `MDNavigationBar`. + """ + + set_bars_color = BooleanProperty(False) + """ + If `True` the background color of the navigation bar will be set + automatically according to the current color of the toolbar. + + .. versionadded:: 1.0.0 + + :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + def __init__(self, *args, **kwargs): + self.register_event_type("on_switch_tabs") + super().__init__(*args, **kwargs) + Clock.schedule_once(self.set_status_bar_color) + + def set_active_item(self, item: MDNavigationItem) -> None: + """Sets the currently active element on the panel.""" + + for widget in self.children: + if item is widget: + widget.active = True + self.dispatch( + "on_switch_tabs", + widget, + widget.ids.icon_container.children[0].icon + if len(widget.ids.icon_container.children) + else "", + widget.ids.label_container.children[0].text + if len(widget.ids.label_container.children) + else "", + ) + else: + widget.active = False + + def set_status_bar_color(self, interval: int | float) -> None: + """Sets the color of the lower system navigation bar.""" + + if self.set_bars_color: + set_bars_colors(self.md_bg_color, None, self.theme_cls.theme_style) + + def on_switch_tabs( + self, item: MDNavigationItem, item_icon: str, item_text: str + ) -> None: + """Fired when switching tabs.""" + + +class MDNavigationItemIconContainer(BoxLayout): + pass + + +class MDNavigationItemLabelContainer(BoxLayout): + pass diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__init__.py index 1e859f9..db6485f 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__init__.py @@ -4,6 +4,9 @@ from .navigationdrawer import ( MDNavigationDrawerDivider, MDNavigationDrawerHeader, MDNavigationDrawerItem, + MDNavigationDrawerItemLeadingIcon, + MDNavigationDrawerItemText, + MDNavigationDrawerItemTrailingText, MDNavigationDrawerLabel, MDNavigationDrawerMenu, MDNavigationLayout, diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/__init__.cpython-311.pyc index bc99a6d..0adf094 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/navigationdrawer.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/navigationdrawer.cpython-311.pyc index 9a6e7e0..2f8adf8 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/navigationdrawer.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/__pycache__/navigationdrawer.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.kv index dbb484a..702fce1 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.kv @@ -1,134 +1,83 @@ #:import Window kivy.core.window.Window -#:import m_res kivymd.material_resources -: + + type: "filled" size_hint_x: None - width: Window.width - dp(56) if Window.width <= dp(376) else dp(320) - md_bg_color: self.theme_cls.bg_light - padding: + width: Window.width - dp(56) if Window.width <= dp(360) else dp(320) + theme_bg_color: "Custom" + shadow_radius: self.radius + md_bg_color: + self.theme_cls.surfaceContainerLowColor \ + if not self.background_color else \ + self.background_color x: (self.width * (self.open_progress - 1)) \ if self.anchor == "left" \ else (Window.width - self.width * self.open_progress) - canvas: - Clear - Color: - rgba: self.md_bg_color - RoundedRectangle: - size: self.size - pos: self.pos - source: root.background - radius: root.radius - adaptive_height: True - - MDLabel: - text: root.text - adaptive_size: True - markup: True - - - - adaptive_height: True - - MDSeparator: - color: root.color if root.color else app.theme_cls.divider_color - - - - adaptive_height: True - - FitImage: - id: logo - source: root.source - size_hint: None, None - size: label_box.height, label_box.height - - MDBoxLayout: - id: label_box - orientation: "vertical" - adaptive_height: True - - MDLabel: - id: title - adaptive_height: True - halign: root.title_halign - text: root.title - font_style: root.title_font_style - font_size: root.title_font_size - color: - root.title_color \ - if root.title_color else \ - app.theme_cls.text_color - - MDLabel: - id: text - adaptive_height: True - text: root.text - halign: root.text_halign - font_style: root.text_font_style - font_size: root.text_font_size - color: - root.text_color \ - if root.text_color else \ - app.theme_cls.text_color + padding: "20dp", "0dp", "16dp", "16dp" + text_color: + self.theme_cls.onSurfaceColor \ + if self.theme_text_color == "Primary" else \ + self.text_color - radius: self.height / 2 if self.radius == [0, 0, 0, 0] else self.radius - divider: None - theme_text_color: "Custom" - text_color: self.text_color if not self.selected else self.selected_color - _txt_left_pad: "56dp" - on_size: - self.ids._left_container.x = "4dp" - self.ids._right_container.width = right_label.texture_size[0] - on_release: - if not self.selected: self._text_color = self.text_color - self._text_right_color = root.text_right_color if root.text_right_color else app.theme_cls.text_color - self._drawer_menu.reset_active_color(self) + radius: self.height / 2 + ripple_color: self.theme_cls.onSecondaryContainerColor[:-1] + [0.12] + theme_bg_color: "Custom" + md_bg_color: + self.theme_cls.surfaceContainerLowColor \ + if not self.inactive_indicator_color else \ + self.inactive_indicator_color - IconLeftWidgetWithoutTouch: - icon: root.icon - theme_icon_color: "Custom" - icon_color: - ( \ - app.theme_cls.text_color \ - if not root.icon_color else \ - root.icon_color \ - ) \ - if not root.selected else \ - root.selected_color - MDLabel: - id: right_label - text: root.right_text - pos_hint: {"center_y": .5} - adaptive_size: True - markup: True - color: - ( \ - root.text_right_color \ - if root.text_right_color else \ - app.theme_cls.text_color \ - ) \ - if not root.selected else \ - root.selected_color - x: - root.x \ - + root.width \ - - m_res.HORIZ_MARGINS \ - - root.ids._right_container.width - dp(24) \ - - self.texture_size[0] \ - + dp(24) + + text_color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_text_color == "Primary" else \ + self.text_color + + + + font_style: "Label" + role: "large" + text_color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_text_color == "Primary" else \ + self.text_color + + + + icon_color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_icon_color == "Primary" else \ + self.icon_color + + + + size_hint_y: None + height: self.minimum_height + + + + padding: 0, "4dp", 0, "4dp" + size_hint_y: None + height: self.minimum_height + + MDDivider: - MDList: + GridLayout: id: menu + cols: 1 + size_hint_y: None + height: self.minimum_height spacing: root.spacing + diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.py index ac2eac3..369869e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationdrawer/navigationdrawer.py @@ -4,19 +4,22 @@ Components/NavigationDrawer .. seealso:: - `Material Design 2 spec, Navigation drawer `_ and - `Material Design 3 spec, Navigation drawer `_ + `Material Design, Navigation drawer `_ .. rubric:: Navigation drawers provide access to destinations in your app. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.png :align: center +- Use navigation drawers in expanded layouts and modal navigation drawers in compact and medium layouts +- Can be open or closed by default +- Two types: standard and modal + When using the class :class:`~MDNavigationDrawer` skeleton of your `KV` markup should look like this: -Anatomy -------- +Usage +----- .. code-block:: kv @@ -32,8 +35,8 @@ Anatomy MDNavigationDrawer: - # This custom rule should implement what will be appear in your - # MDNavigationDrawer. + # This custom rule should implement what will be displayed in + # your MDNavigationDrawer. ContentNavigationDrawer: A simple example @@ -47,11 +50,11 @@ A simple example from kivy.lang import Builder - from kivymd.uix.boxlayout import MDBoxLayout from kivymd.app import MDApp KV = ''' MDScreen: + md_bg_color: self.theme_cls.backgroundColor MDNavigationLayout: @@ -59,31 +62,39 @@ A simple example MDScreen: - MDTopAppBar: - title: "Navigation Drawer" - elevation: 4 - pos_hint: {"top": 1} - md_bg_color: "#e7e4c0" - specific_text_color: "#4a4939" - left_action_items: - [['menu', lambda x: nav_drawer.set_state("open")]] + MDButton: + pos_hint: {"center_x": .5, "center_y": .5} + on_release: nav_drawer.set_state("toggle") + MDButtonText: + text: "Open Drawer" MDNavigationDrawer: id: nav_drawer - radius: (0, 16, 16, 0) + radius: 0, dp(16), dp(16), 0 - ContentNavigationDrawer: + MDNavigationDrawerMenu: + + MDNavigationDrawerLabel: + text: "Mail" + + MDNavigationDrawerItem: + + MDNavigationDrawerItemLeadingIcon: + icon: "account" + + MDNavigationDrawerItemText: + text: "Inbox" + + MDNavigationDrawerItemTrailingText: + text: "24" + + MDNavigationDrawerDivider: ''' - class ContentNavigationDrawer(MDBoxLayout): - pass - - class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" return Builder.load_string(KV) @@ -93,454 +104,416 @@ A simple example .. code-block:: python - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.navigationdrawer import MDNavigationLayout, MDNavigationDrawer - from kivymd.uix.screen import MDScreen + from kivy.metrics import dp + + from kivymd.uix.button import MDButton, MDButtonText from kivymd.uix.screenmanager import MDScreenManager - from kivymd.uix.toolbar import MDTopAppBar - - - class ContentNavigationDrawer(MDBoxLayout): - pass + from kivymd.uix.navigationdrawer import ( + MDNavigationLayout, + MDNavigationDrawer, + MDNavigationDrawerMenu, + MDNavigationDrawerLabel, + MDNavigationDrawerItem, + MDNavigationDrawerItemLeadingIcon, + MDNavigationDrawerItemText, + MDNavigationDrawerItemTrailingText, + MDNavigationDrawerDivider, + ) + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" - return( - MDScreen( - MDNavigationLayout( - MDScreenManager( - MDScreen( - MDTopAppBar( - title="Navigation Drawer", - elevation=4, - pos_hint={"top": 1}, - md_bg_color="#e7e4c0", - specific_text_color="#4a4939", - left_action_items=[ - ['menu', lambda x: self.nav_drawer_open()] - ], - ) - - ) - ), - MDNavigationDrawer( - ContentNavigationDrawer(), - id="nav_drawer", - radius=(0, 16, 16, 0), + return MDScreen( + MDNavigationLayout( + MDScreenManager( + MDScreen( + MDButton( + MDButtonText( + text="Open Drawer", + ), + on_release=lambda x: self.root.get_ids().nav_drawer.set_state( + "toggle" + ), + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ), ), ), + MDNavigationDrawer( + MDNavigationDrawerMenu( + MDNavigationDrawerLabel( + text="Mail", + ), + MDNavigationDrawerItem( + MDNavigationDrawerItemLeadingIcon( + icon="account", + ), + MDNavigationDrawerItemText( + text="Inbox", + ), + MDNavigationDrawerItemTrailingText( + text="24", + ), + ), + MDNavigationDrawerDivider( + ), + ), + id="nav_drawer", + radius=(0, dp(16), dp(16), 0), + ), ), + md_bg_color=self.theme_cls.backgroundColor, ) - def nav_drawer_open(self, *args): - nav_drawer = self.root.children[0].ids.nav_drawer - nav_drawer.set_state("open") - Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer.gif +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-anatomy.png :align: center .. Note:: :class:`~MDNavigationDrawer` is an empty :class:`~kivymd.uix.card.MDCard` panel. -Standard content for the navigation bar ---------------------------------------- +Item anatomy +------------ -.. tabs:: +.. code-block:: kv - .. tab:: Declarative KV styles + MDNavigationDrawerItem: - .. code-block:: python + MDNavigationDrawerItemLeadingIcon: + icon: "account" - from kivy.lang import Builder + MDNavigationDrawerItemText: + text: "Inbox" - from kivymd.app import MDApp + MDNavigationDrawerItemTrailingText: + text: "24" - KV = ''' - - focus_color: "#e7e4c0" - text_color: "#4a4939" - icon_color: "#4a4939" - ripple_color: "#c5bdd2" - selected_color: "#0c6c4d" - - - - text_color: "#4a4939" - icon_color: "#4a4939" - focus_behavior: False - selected_color: "#4a4939" - _no_ripple_effect: True - - - MDScreen: - - MDNavigationLayout: - - MDScreenManager: - - MDScreen: - - MDTopAppBar: - title: "Navigation Drawer" - elevation: 4 - pos_hint: {"top": 1} - md_bg_color: "#e7e4c0" - specific_text_color: "#4a4939" - left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] - - MDNavigationDrawer: - id: nav_drawer - radius: (0, 16, 16, 0) - - MDNavigationDrawerMenu: - - MDNavigationDrawerHeader: - title: "Header title" - title_color: "#4a4939" - text: "Header text" - spacing: "4dp" - padding: "12dp", 0, 0, "56dp" - - MDNavigationDrawerLabel: - text: "Mail" - - DrawerClickableItem: - icon: "gmail" - right_text: "+99" - text_right_color: "#4a4939" - text: "Inbox" - - DrawerClickableItem: - icon: "send" - text: "Outbox" - - MDNavigationDrawerDivider: - - MDNavigationDrawerLabel: - text: "Labels" - - DrawerLabelItem: - icon: "information-outline" - text: "Label" - - DrawerLabelItem: - icon: "information-outline" - text: "Label" - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - - .. tab:: Declarative python styles - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.navigationdrawer import ( - MDNavigationLayout, - MDNavigationDrawer, - MDNavigationDrawerMenu, - MDNavigationDrawerHeader, - MDNavigationDrawerLabel, - MDNavigationDrawerDivider, - MDNavigationDrawerItem, - ) - from kivymd.uix.screen import MDScreen - from kivymd.uix.screenmanager import MDScreenManager - from kivymd.uix.toolbar import MDTopAppBar - - - class BaseNavigationDrawerItem(MDNavigationDrawerItem): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.radius = 24 - self.text_color = "#4a4939" - self.icon_color = "#4a4939" - self.focus_color = "#e7e4c0" - - - class DrawerLabelItem(BaseNavigationDrawerItem): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.focus_behavior = False - self._no_ripple_effect = True - self.selected_color = "#4a4939" - - - class DrawerClickableItem(BaseNavigationDrawerItem): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.ripple_color = "#c5bdd2" - self.selected_color = "#0c6c4d" - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return( - MDScreen( - MDNavigationLayout( - MDScreenManager( - MDScreen( - MDTopAppBar( - title="Navigation Drawer", - elevation=4, - pos_hint={"top": 1}, - md_bg_color="#e7e4c0", - specific_text_color="#4a4939", - left_action_items=[ - ['menu', lambda x: self.nav_drawer_open()] - ], - ) - - ) - ), - MDNavigationDrawer( - MDNavigationDrawerMenu( - MDNavigationDrawerHeader( - title="Header title", - title_color="#4a4939", - text="Header text", - spacing="4dp", - padding=("12dp", 0, 0, "56dp"), - ), - MDNavigationDrawerLabel( - text="Mail", - ), - DrawerClickableItem( - icon="gmail", - right_text="+99", - text_right_color="#4a4939", - text="Inbox", - ), - DrawerClickableItem( - icon="send", - text="Outbox", - ), - MDNavigationDrawerDivider(), - MDNavigationDrawerLabel( - text="Labels", - ), - DrawerLabelItem( - icon="information-outline", - text="Label", - ), - DrawerLabelItem( - icon="information-outline", - text="Label", - ), - ), - id="nav_drawer", - radius=(0, 16, 16, 0), - ) - ) - ) - ) - - def nav_drawer_open(self, *args): - nav_drawer = self.root.children[0].ids.nav_drawer - nav_drawer.set_state("open") - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standatd-content.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-item-anatomy.png :align: center -Switching screens in the ``ScreenManager`` and using the common ``MDTopAppBar`` ------------------------------------------------------------------------------ +Type drawer +=========== -.. tabs:: +Standard +-------- - .. tab:: Declarative KV styles +.. code-block:: kv - .. code-block:: python + MDNavigationDrawer: + drawer_type: "standard" - from kivy.lang import Builder - from kivy.properties import ObjectProperty +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-type-standard.gif + :align: center - from kivymd.app import MDApp - from kivymd.uix.scrollview import MDScrollView +Modal +----- - KV = ''' - +.. code-block:: kv - MDList: + MDNavigationDrawer: + drawer_type: "modal" - OneLineListItem: - text: "Screen 1" - on_press: - root.nav_drawer.set_state("close") - root.screen_manager.current = "scr 1" +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-type-modal.gif + :align: center - OneLineListItem: - text: "Screen 2" - on_press: - root.nav_drawer.set_state("close") - root.screen_manager.current = "scr 2" +Anchoring screen edge for drawer +================================ + + Left + ---- + + .. code-block:: kv + + MDNavigationDrawer: + anchor: "left" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-ancjor-left.png + :align: center + + Right + ----- + + .. code-block:: kv + + MDNavigationDrawer: + anchor: "right" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-ancjor-right.png + :align: center + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + + focus_color: "#e7e4c0" + text_color: "#4a4939" + icon_color: "#4a4939" + ripple_color: "#c5bdd2" + selected_color: "#0c6c4d" - MDScreen: - - MDTopAppBar: - pos_hint: {"top": 1} - elevation: 4 - title: "MDNavigationDrawer" - left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]] - - MDNavigationLayout: - - MDScreenManager: - id: screen_manager - - MDScreen: - name: "scr 1" - - MDLabel: - text: "Screen 1" - halign: "center" - - MDScreen: - name: "scr 2" - - MDLabel: - text: "Screen 2" - halign: "center" - - MDNavigationDrawer: - id: nav_drawer - radius: (0, 16, 16, 0) - - ContentNavigationDrawer: - screen_manager: screen_manager - nav_drawer: nav_drawer - ''' + + text_color: "#4a4939" + icon_color: "#4a4939" + focus_behavior: False + selected_color: "#4a4939" + _no_ripple_effect: True - class ContentNavigationDrawer(MDScrollView): - screen_manager = ObjectProperty() - nav_drawer = ObjectProperty() + MDScreen: + + MDNavigationLayout: + + MDScreenManager: + + MDScreen: + + MDRaisedButton: + text: "Open Drawer" + pos_hint: {"center_x": .5, "center_y": .5} + on_release: nav_drawer.set_state("toggle") + + MDNavigationDrawer: + id: nav_drawer + radius: (0, dp(16), dp(16), 0) + + MDNavigationDrawerMenu: + + MDNavigationDrawerHeader: + title: "Header title" + title_color: "#4a4939" + text: "Header text" + spacing: "4dp" + padding: "12dp", 0, 0, "56dp" + + MDNavigationDrawerLabel: + text: "Mail" + + DrawerClickableItem: + icon: "gmail" + right_text: "+99" + text_right_color: "#4a4939" + text: "Inbox" + + DrawerClickableItem: + icon: "send" + text: "Outbox" + + MDNavigationDrawerDivider: + + MDNavigationDrawerLabel: + text: "Labels" + + DrawerLabelItem: + icon: "information-outline" + text: "Label" + + DrawerLabelItem: + icon: "information-outline" + text: "Label" + ''' - class Example(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + return Builder.load_string(KV) - Example().run() + Example().run() - .. tab:: Declarative python styles +2.2.0 version +------------- - .. code-block:: python +.. code-block:: python - from kivymd.app import MDApp - from kivymd.uix.label import MDLabel - from kivymd.uix.list import MDList, OneLineListItem - from kivymd.uix.navigationdrawer import MDNavigationLayout, MDNavigationDrawer - from kivymd.uix.screen import MDScreen - from kivymd.uix.screenmanager import MDScreenManager - from kivymd.uix.scrollview import MDScrollView - from kivymd.uix.toolbar import MDTopAppBar + from kivy.lang import Builder + from kivy.properties import StringProperty, ColorProperty + + from kivymd.app import MDApp + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.navigationdrawer import ( + MDNavigationDrawerItem, MDNavigationDrawerItemTrailingText + ) + + KV = ''' + + active_indicator_color: "#e7e4c0" + + MDNavigationDrawerItemLeadingIcon: + icon: root.icon + theme_icon_color: "Custom" + icon_color: "#4a4939" + + MDNavigationDrawerItemText: + text: root.text + theme_text_color: "Custom" + text_color: "#4a4939" - class Example(MDApp): - def build(self): - self.theme_cls.primary_palette = "Orange" - self.theme_cls.theme_style = "Dark" - return ( - MDScreen( - MDTopAppBar( - pos_hint={"top": 1}, - elevation=4, - title="MDNavigationDrawer", - left_action_items=[["menu", lambda x: self.nav_drawer_open()]], - ), - MDNavigationLayout( - MDScreenManager( - MDScreen( - MDLabel( - text="Screen 1", - halign="center", - ), - name="scr 1", - ), - MDScreen( - MDLabel( - text="Screen 2", - halign="center", - ), - name="scr 2", - ), - id="screen_manager", - ), - MDNavigationDrawer( - MDScrollView( - MDList( - OneLineListItem( - text="Screen 1", - on_press=self.switch_screen, - ), - OneLineListItem( - text="Screen 2", - on_press=self.switch_screen, - ), - ), - ), - id="nav_drawer", - radius=(0, 16, 16, 0), - ), - id="navigation_layout", - ) - ) - ) + + adaptive_height: True + padding: "18dp", 0, 0, "12dp" - def switch_screen(self, instance_list_item: OneLineListItem): - self.root.ids.navigation_layout.ids.screen_manager.current = { - "Screen 1": "scr 1", "Screen 2": "scr 2" - }[instance_list_item.text] - self.root.children[0].ids.nav_drawer.set_state("close") + MDNavigationDrawerItemLeadingIcon: + icon: root.icon + theme_icon_color: "Custom" + icon_color: "#4a4939" + pos_hint: {"center_y": .5} - def nav_drawer_open(self): - nav_drawer = self.root.children[0].ids.nav_drawer - nav_drawer.set_state("open") + MDNavigationDrawerLabel: + text: root.text + theme_text_color: "Custom" + text_color: "#4a4939" + pos_hint: {"center_y": .5} + padding: "6dp", 0, "16dp", 0 + theme_line_height: "Custom" + line_height: 0 - Example().run() + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDNavigationLayout: + + MDScreenManager: + + MDScreen: + + MDButton: + pos_hint: {"center_x": .5, "center_y": .5} + on_release: nav_drawer.set_state("toggle") + + MDButtonText: + text: "Open Drawer" + + MDNavigationDrawer: + id: nav_drawer + radius: 0, dp(16), dp(16), 0 + + MDNavigationDrawerMenu: + + MDNavigationDrawerHeader: + orientation: "vertical" + padding: 0, 0, 0, "12dp" + adaptive_height: True + + MDLabel: + text: "Header title" + theme_text_color: "Custom" + theme_line_height: "Custom" + line_height: 0 + text_color: "#4a4939" + adaptive_height: True + padding_x: "16dp" + font_style: "Display" + role: "small" + + MDLabel: + text: "Header text" + padding_x: "18dp" + adaptive_height: True + font_style: "Title" + role: "large" + + MDNavigationDrawerDivider: + + DrawerItem: + icon: "gmail" + text: "Inbox" + trailing_text: "+99" + trailing_text_color: "#4a4939" + + DrawerItem: + icon: "send" + text: "Outbox" + + MDNavigationDrawerDivider: + + MDNavigationDrawerLabel: + text: "Labels" + padding_y: "12dp" + + DrawerLabel: + icon: "information-outline" + text: "Label" + + DrawerLabel: + icon: "information-outline" + text: "Label" + ''' + + + class DrawerLabel(MDBoxLayout): + icon = StringProperty() + text = StringProperty() + + + class DrawerItem(MDNavigationDrawerItem): + icon = StringProperty() + text = StringProperty() + trailing_text = StringProperty() + trailing_text_color = ColorProperty() + + _trailing_text_obj = None + + def on_trailing_text(self, instance, value): + self._trailing_text_obj = MDNavigationDrawerItemTrailingText( + text=value, + theme_text_color="Custom", + text_color=self.trailing_text_color, + ) + self.add_widget(self._trailing_text_obj) + + def on_trailing_text_color(self, instance, value): + self._trailing_text_obj.text_color = value + + + class Example(MDApp): + def build(self): + return Builder.load_string(KV) + + + Example().run() """ __all__ = ( "MDNavigationLayout", "MDNavigationDrawer", "MDNavigationDrawerItem", + "MDNavigationDrawerItemLeadingIcon", + "MDNavigationDrawerItemTrailingText", + "MDNavigationDrawerItemText", "MDNavigationDrawerMenu", "MDNavigationDrawerHeader", "MDNavigationDrawerLabel", "MDNavigationDrawerDivider", + "BaseNavigationDrawerItem", ) import os -from typing import Union from kivy.animation import Animation, AnimationTransition -from kivy.clock import Clock from kivy.core.window import Window from kivy.graphics.context_instructions import Color from kivy.graphics.vertex_instructions import Rectangle from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( AliasProperty, BooleanProperty, @@ -551,16 +524,24 @@ from kivy.properties import ( StringProperty, VariableListProperty, ) +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.gridlayout import GridLayout from kivy.uix.screenmanager import ScreenManager +from kivymd.uix.appbar import MDTopAppBar +from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.label import MDLabel from kivymd import uix_path from kivymd.uix.behaviors.focus_behavior import FocusBehavior -from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.card import MDCard -from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.list import MDList, OneLineAvatarIconListItem +from kivymd.uix.list import ( + MDListItem, + MDListItemLeadingIcon, + MDListItemSupportingText, + MDListItemTrailingSupportingText, +) from kivymd.uix.scrollview import MDScrollView -from kivymd.uix.toolbar import MDTopAppBar with open( os.path.join(uix_path, "navigationdrawer", "navigationdrawer.kv"), @@ -573,10 +554,33 @@ class NavigationDrawerContentError(Exception): pass -class MDNavigationLayout(MDFloatLayout): +class BaseNavigationDrawerItem: + """ + Implement the base class for the menu list item. + + .. versionadded:: 2.0.0 + """ + + selected = BooleanProperty(False) + """ + Is the item selected. + + :attr:`selected` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + # kivymd.uix.navigationdrawer.MDNavigationDrawerMenu object. + _drawer_menu = ObjectProperty() + # kivymd.uix.navigationdrawer.MDNavigationDrawerItem object. + _drawer_item = ObjectProperty() + + +class MDNavigationLayout(DeclarativeBehavior, FloatLayout): """ For more information, see in the - :class:`~kivymd.uix.floatlayout.MDFloatLayout` class documentation. + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.floatlayout.FloatLayout` + classes documentation. """ _scrim_color = ObjectProperty(None) @@ -593,15 +597,21 @@ class MDNavigationLayout(MDFloatLayout): manager = self._screen_manager if not drawer or not manager: return - if drawer.type == "standard": + if drawer.drawer_type == "standard": manager.size_hint_x = None if drawer.anchor == "left": - manager.x = drawer.width + drawer.x - manager.width = self.width - manager.x + if ( + self._navigation_drawer.__class__.__name__ + != "MDBottomSheet" + ): + manager.x = drawer.width + drawer.x + manager.width = self.width - manager.x + else: + manager.width = self.width - manager.x else: manager.x = 0 manager.width = drawer.x - elif drawer.type == "modal": + elif drawer.drawer_type == "modal": manager.size_hint_x = None manager.x = 0 if drawer.anchor == "left": @@ -634,7 +644,8 @@ class MDNavigationLayout(MDFloatLayout): """ if not isinstance( - widget, (MDNavigationDrawer, ScreenManager, MDTopAppBar) + widget, + (MDNavigationDrawer, ScreenManager, MDTopAppBar), ): raise NavigationDrawerContentError( "The MDNavigationLayout must contain " @@ -656,323 +667,141 @@ class MDNavigationLayout(MDFloatLayout): return super().add_widget(widget) -class MDNavigationDrawerLabel(MDBoxLayout): +class MDNavigationDrawerLabel(MDLabel): """ - Implements a label for a menu for :class:`~MDNavigationDrawer` class. + Implements a label class. - For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` + For more information, see in the :class:`~kivymd.uix.label.label.MDLabel` class documentation. .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDNavigationDrawer: - - MDNavigationDrawerMenu: - - MDNavigationDrawerLabel: - text: "Mail" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-label.png - :align: center - """ - - text = StringProperty() - """ - Text label. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - padding = VariableListProperty(["20dp", 0, 0, "8dp"]) - """ - Padding between layout box and children: [padding_left, padding_top, - padding_right, padding_bottom]. - - Padding also accepts a two argument form [padding_horizontal, - padding_vertical] and a one argument form [padding]. - - :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` - and defaults to `['20dp', 0, 0, '8dp']`. """ -class MDNavigationDrawerDivider(MDBoxLayout): +class MDNavigationDrawerDivider(BoxLayout): """ - Implements a divider for a menu for :class:`~MDNavigationDrawer` class. + Implements a divider class. - For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` - class documentation. + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDNavigationDrawer: - - MDNavigationDrawerMenu: - - MDNavigationDrawerLabel: - text: "Mail" - - MDNavigationDrawerDivider: - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-divider.png - :align: center - """ - - padding = VariableListProperty(["20dp", "12dp", 0, "12dp"]) - """ - Padding between layout box and children: [padding_left, padding_top, - padding_right, padding_bottom]. - - Padding also accepts a two argument form [padding_horizontal, - padding_vertical] and a one argument form [padding]. - - :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` - and defaults to `['20dp', '12dp', 0, '12dp']`. - """ - - color = ColorProperty(None) - """ - Divider color in (r, g, b, a) or string format. - - :attr:`color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. """ -class MDNavigationDrawerHeader(MDBoxLayout): +class MDNavigationDrawerHeader(DeclarativeBehavior, BoxLayout): """ - Implements a header for a menu for :class:`~MDNavigationDrawer` class. + Implements a header class. - For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` - class documentation. + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDNavigationDrawer: - - MDNavigationDrawerMenu: - - MDNavigationDrawerHeader: - title: "Header title" - text: "Header text" - spacing: "4dp" - padding: "12dp", 0, 0, "56dp" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-header.png - :align: center """ - source = StringProperty() - """ - Image logo path. - .. code-block:: kv - - MDNavigationDrawer: - - MDNavigationDrawerMenu: - - MDNavigationDrawerHeader: - title: "Header title" - text: "Header text" - source: "logo.png" - spacing: "4dp" - padding: "12dp", 0, 0, "56dp" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-header-source.png - :align: center - - :attr:`source` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title = StringProperty() - """ - Title shown in the first line. - - :attr:`title` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_halign = StringProperty("left") - """ - Title halign first line. - - :attr:`title_halign` is a :class:`~kivy.properties.StringProperty` - and defaults to `'left'`. - """ - - title_color = ColorProperty(None) - """ - Title text color in (r, g, b, a) or string format. - - :attr:`title_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - title_font_style = StringProperty("H4") - """ - Title shown in the first line. - - :attr:`title_font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'H4'`. - """ - - title_font_size = StringProperty("34sp") - """ - Title shown in the first line. - - :attr:`title_font_size` is a :class:`~kivy.properties.StringProperty` - and defaults to `'34sp'`. - """ - - text = StringProperty() - """ - Text shown in the second line. - - :attr:`text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text_halign = StringProperty("left") - """ - Text halign first line. - - :attr:`text_halign` is a :class:`~kivy.properties.StringProperty` - and defaults to `'left'`. - """ - - text_color = ColorProperty(None) - """ - Title text color in (r, g, b, a) or string format. - - :attr:`text_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_font_style = StringProperty("H6") - """ - Title shown in the first line. - - :attr:`text_font_style` is a :class:`~kivy.properties.StringProperty` - and defaults to `'H6'`. - """ - - text_font_size = StringProperty("20sp") - """ - Title shown in the first line. - - :attr:`text_font_size` is a :class:`~kivy.properties.StringProperty` - and defaults to `'20sp'`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.check_content) - - def check_content(self, interval: Union[int, float]) -> None: - """Removes widgets that the user has not added to the container.""" - - if not self.title: - self.ids.label_box.remove_widget(self.ids.title) - if not self.text: - self.ids.label_box.remove_widget(self.ids.text) - if not self.source: - self.remove_widget(self.ids.logo) - - -class MDNavigationDrawerItem(OneLineAvatarIconListItem, FocusBehavior): +class MDNavigationDrawerItem( + MDListItem, FocusBehavior, BaseNavigationDrawerItem +): """ Implements an item for the :class:`~MDNavigationDrawer` menu list. For more information, see in the - :class:`~kivymd.uix.list.OneLineAvatarIconListItem` and - :class:`~kivymd.uix.behaviors.FocusBehavior` - class documentation. + :class:`~kivymd.uix.list.list.MDListItem` and + :class:`~kivymd.uix.behaviors.focus_behavior.FocusBehavior` and + :class:`~BaseNavigationDrawerItem` + classes documentation. .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDNavigationDrawer: - - MDNavigationDrawerMenu: - - MDNavigationDrawerHeader: - title: "Header title" - text: "Header text" - spacing: "4dp" - padding: "12dp", 0, 0, "56dp" - - MDNavigationDrawerItem - icon: "gmail" - right_text: "+99" - text: "Inbox" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-item.png - :align: center """ - selected = BooleanProperty(False) + active_indicator_color = ColorProperty(None) """ - Is the item selected. + The active indicator color in (r, g, b, a) or string format. - :attr:`selected` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ + .. versionadded:: 2.0.0 - icon = StringProperty() - """ - Icon item. - - :attr:`icon` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_color = ColorProperty(None) - """ - Icon color in (r, g, b, a) or string format item. - - :attr:`icon_color` is a :class:`~kivy.properties.ColorProperty` + :attr:`active_indicator_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - selected_color = ColorProperty([0, 0, 0, 1]) + inactive_indicator_color = ColorProperty(None) """ - The color in (r, g, b, a) or string format of the icon and text of the - selected item. + The inactive indicator color in (r, g, b, a) or string format. - :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 1]`. - """ + .. versionadded:: 2.0.0 - right_text = StringProperty() - """ - Right text item. - - :attr:`right_text` is a :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text_right_color = ColorProperty(None) - """ - Right text color item in (r, g, b, a) or string format. - - :attr:`text_right_color` is a :class:`~kivy.properties.ColorProperty` + :attr:`inactive_indicator_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - _text_color = None - _text_right_color = None - # kivymd.uix.navigationdrawer.navigationdrawer.MDNavigationDrawerMenu - _drawer_menu = ObjectProperty() + def add_widget(self, widget, *args, **kwargs): + if isinstance( + widget, + ( + MDNavigationDrawerItemLeadingIcon, + MDNavigationDrawerItemText, + MDNavigationDrawerItemTrailingText, + ), + ): + widget._drawer_item = self + return super().add_widget(widget) + + def on_release(self, *args) -> None: + """ + Fired when the item is released + (i.e. the touch/click that pressed the item goes away). + """ + + self.selected = not self.selected + self._drawer_menu.update_items_color(self) + + +class MDNavigationDrawerItemLeadingIcon( + MDListItemLeadingIcon, BaseNavigationDrawerItem +): + """ + Implements the leading icon for the menu list item. + + For more information, see in the + :class:`~kivymd.uix.list.list.MDListItemLeadingIcon` and + :class:`~BaseNavigationDrawerItem` + classes documentation. + + .. versionadded:: 2.0.0 + """ + + +class MDNavigationDrawerItemText( + MDListItemSupportingText, BaseNavigationDrawerItem +): + """ + Implements the text for the menu list item. + + For more information, see in the + :class:`~kivymd.uix.list.list.MDListItemSupportingText` and + :class:`~BaseNavigationDrawerItem` + classes documentation. + + .. versionadded:: 2.0.0 + """ + + +class MDNavigationDrawerItemTrailingText( + MDListItemTrailingSupportingText, BaseNavigationDrawerItem +): + """ + Implements the supporting text for the menu list item. + + For more information, see in the + :class:`~kivymd.uix.list.list.MDListItemTrailingSupportingText` and + :class:`~BaseNavigationDrawerItem` + classes documentation. + + .. versionadded:: 2.0.0 + """ class MDNavigationDrawerMenu(MDScrollView): @@ -1004,63 +833,59 @@ class MDNavigationDrawerMenu(MDScrollView): """ def add_widget(self, widget, *args, **kwargs): - if isinstance(widget, MDList): + if isinstance(widget, GridLayout): return super().add_widget(widget, *args, **kwargs) else: if isinstance(widget, MDNavigationDrawerItem): widget._drawer_menu = self self.ids.menu.add_widget(widget) - def reset_active_color(self, item: MDNavigationDrawerItem) -> None: + def update_items_color(self, item: MDNavigationDrawerItem) -> None: for widget in self.ids.menu.children: if issubclass(widget.__class__, MDNavigationDrawerItem): - if widget != item: - widget.selected = False + if widget is not item: + widget.md_bg_color = ( + widget.theme_cls.surfaceContainerLowColor + if not widget.inactive_indicator_color + else widget.inactive_indicator_color + ) else: - widget.selected = True - - if ( - issubclass(widget.__class__, MDNavigationDrawerItem) - and widget != item - ): - if widget._text_color: - widget.text_color = widget._text_color + widget.md_bg_color = ( + widget.theme_cls.secondaryContainerColor + if not widget.active_indicator_color + else widget.active_indicator_color + ) class MDNavigationDrawer(MDCard): - type = OptionProperty("modal", options=("standard", "modal")) + """ + Navigation drawer class. + + For more information, see in the :class:`~kivymd.uix.card.card.MDCard` + class documentation. + + :Events: + + .. versionadded:: 2.0.0 + + `on_open`: + Fired when the navigation drawer is opened. + `on_close`: + Fired when the navigation drawer is closed. + """ + + drawer_type = OptionProperty("modal", options=("standard", "modal")) """ Type of drawer. Modal type will be on top of screen. Standard type will be at left or right of screen. Also it automatically disables :attr:`close_on_click` and :attr:`enable_swiping` to prevent closing drawer for standard type. - For more information, see in the :class:`~kivymd.uix.card.MDCard` - class documentation. + .. versionchanged:: 2.0.0 - Standard - -------- + Rename from `type` to `drawer_type`. - .. code-block:: kv - - MDNavigationDrawer: - type: "standard" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-standard.gif - :align: center - - Modal - ----- - - .. code-block:: kv - - MDNavigationDrawer: - type: "modal" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-modal.gif - :align: center - - :attr:`type` is a :class:`~kivy.properties.OptionProperty` + :attr:`drawer_type` is a :class:`~kivy.properties.OptionProperty` and defaults to `'modal'`. """ @@ -1069,28 +894,6 @@ class MDNavigationDrawer(MDCard): Anchoring screen edge for drawer. Set it to `'right'` for right-to-left languages. Available options are: `'left'`, `'right'`. - Left - ---- - - .. code-block:: kv - - MDNavigationDrawer: - anchor: "left" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-type-left.png - :align: center - - Right - ----- - - .. code-block:: kv - - MDNavigationDrawer: - anchor: "right" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-type-right.png - :align: center - :attr:`anchor` is a :class:`~kivy.properties.OptionProperty` and defaults to `'left'`. """ @@ -1102,20 +905,11 @@ class MDNavigationDrawer(MDCard): multiplied with :attr:`_scrim_alpha`. Set fourth channel to 0 if you want to disable scrim. - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-scrim-color.png - :align: center - - .. code-block:: kv - - MDNavigationDrawer: - scrim_color: 0, 0, 0, .8 - # scrim_color: 0, 0, 0, .2 - :attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `[0, 0, 0, 0.5]`. """ - padding = VariableListProperty([16, 16, 12, 16]) + padding = VariableListProperty([dp(16), dp(16), dp(12), dp(16)]) """ Padding between layout box and children: [padding_left, padding_top, padding_right, padding_bottom]. @@ -1128,13 +922,13 @@ class MDNavigationDrawer(MDCard): .. code-block:: kv MDNavigationDrawer: - padding: 56, 56, 12, 16 + padding: "56dp" .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-drawer-padding.png :align: center :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and - defaults to '[16, 16, 12, 16]'. + defaults to '[dp(16), dp(16), dp(12), dp(16)]'. """ close_on_click = BooleanProperty(True) @@ -1215,7 +1009,7 @@ class MDNavigationDrawer(MDCard): def _get_scrim_alpha(self): _scrim_alpha = 0 - if self.type == "modal": + if self.drawer_type == "modal": _scrim_alpha = self._scrim_alpha_transition(self.open_progress) if ( isinstance(self.parent, MDNavigationLayout) @@ -1288,8 +1082,42 @@ class MDNavigationDrawer(MDCard): and defaults to `0.2`. """ + background_color = ColorProperty(None) + """ + The drawer background color in (r, g, b, a) or string format. + + .. versionadded:: 2.0.0 + + :attr:`background_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + theme_elevation_level = "Custom" + """ + Drawer elevation level scheme name. + + .. versionadded:: 2.0.0 + + Available options are: `'Primary'`, `'Custom'`. + + :attr:`theme_elevation_level` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'Custom'`. + """ + + elevation_level = 1 + """ + Drawer elevation level (values from 0 to 5) + + .. versionadded:: 2.2.0 + + :attr:`elevation_level` is an :class:`~kivy.properties.BoundedNumericProperty` + and defaults to `2`. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.register_event_type("on_open") + self.register_event_type("on_close") self.bind( open_progress=self.update_status, status=self.update_status, @@ -1297,6 +1125,9 @@ class MDNavigationDrawer(MDCard): ) Window.bind(on_keyboard=self._handle_keyboard) + def set_properties_widget(self) -> None: + pass + def set_state(self, new_state="toggle", animation=True) -> None: """ Change state of the side panel. @@ -1310,26 +1141,30 @@ class MDNavigationDrawer(MDCard): Animation.cancel_all(self, "open_progress") self.status = "opening_with_animation" if animation: - Animation( + anim = Animation( open_progress=1.0, d=self.opening_time * (1 - self.open_progress), t=self.opening_transition, - ).start(self) + ) + anim.bind(on_complete=self._check_state) + anim.start(self) else: self.open_progress = 1 else: # "close" Animation.cancel_all(self, "open_progress") self.status = "closing_with_animation" if animation: - Animation( + anim = Animation( open_progress=0.0, d=self.closing_time * self.open_progress, t=self.closing_transition, - ).start(self) + ) + anim.bind(on_complete=self._check_state) + anim.start(self) else: self.open_progress = 0 - def update_status(self, *_) -> None: + def update_status(self, *args) -> None: status = self.status if status == "closed": self.state = "close" @@ -1365,7 +1200,7 @@ class MDNavigationDrawer(MDCard): for child in self.children[:]: if child.dispatch("on_touch_down", touch): return True - if self.type == "standard" and not self.collide_point( + if self.drawer_type == "standard" and not self.collide_point( touch.ox, touch.oy ): return False @@ -1412,26 +1247,44 @@ class MDNavigationDrawer(MDCard): touch.ox, touch.oy ): self.set_state("close", animation=True) - elif self.type == "standard" and not self.collide_point( + elif self.drawer_type == "standard" and not self.collide_point( touch.ox, touch.oy ): return False elif self.status == "closed": return False - return True + return super().on_touch_up(touch) def on_radius(self, instance_navigation_drawer, radius_value: list) -> None: + """Fired when the :attr:`radius` value changes.""" + self._radius = radius_value - def on_type(self, instance_navigation_drawer, drawer_type: str) -> None: - if self.type == "standard": + def on_drawer_type( + self, instance_navigation_drawer, drawer_type: str + ) -> None: + """Fired when the :attr:`drawer_type` value changes.""" + + if self.drawer_type == "standard": self.enable_swiping = False self.close_on_click = False else: self.enable_swiping = True self.close_on_click = True + def on_open(self, *args) -> None: + """Fired when the navigation drawer is opened.""" + + def on_close(self, *args) -> None: + """Fired when the navigation drawer is closed.""" + def _handle_keyboard(self, window, key, *largs): if key == 27 and self.status == "opened" and self.close_on_click: self.set_state("close") return True + + def _check_state(self, *args): + if self.state == "open": + self.dispatch("on_open") + elif self.state == "close": + self.dispatch("on_close") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__init__.py index 7d3ff75..9076b4c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__init__.py @@ -2,6 +2,8 @@ from .navigationrail import ( MDNavigationRail, MDNavigationRailFabButton, + MDNavigationRailItemIcon, + MDNavigationRailItemLabel, MDNavigationRailItem, MDNavigationRailMenuButton, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/__init__.cpython-311.pyc index d216bbf..0c4e8e3 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/navigationrail.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/navigationrail.cpython-311.pyc index d109f17..85ca0cc 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/navigationrail.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/__pycache__/navigationrail.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.kv index e2623bb..712afdb 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.kv @@ -1,157 +1,109 @@ - pos_hint: {"center_x": .5, "top": 1} + pos_hint: {"center_x": .5} + theme_icon_color: "Custom" + icon_color: self.theme_cls.onSurfaceVariantColor type: "standard" pos_hint: {"center_x": .5} - - - - size_hint: None, 1 - width: "80dp" - - PanelRoot: - id: box_buttons - - PanelItems: - id: box_items - orientation: "vertical" - spacing: "12dp" - adaptive_size: True - pos_hint: {"center_x": .5} + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.primaryColor + theme_icon_color: "Custom" + icon_color: self.theme_cls.onPrimaryColor orientation: "vertical" size_hint: None, None - size: self.navigation_rail.width if self.navigation_rail else 100, "56dp" + width: "80dp" + height: self.minimum_height + spacing: + ( \ + { \ + "selected": "8dp", \ + "labeled": "16dp", \ + "unselected": "16dp", \ + }[self._navigation_rail.type] \ + ) \ + if self._navigation_rail else 0 - RelativeLayout: - id: container + + + canvas.before: + Color: + rgba: + ( \ + ( \ + self.theme_cls.secondaryContainerColor \ + if not self.active_indicator_color else \ + self.active_indicator_color \ + )[:-1] + [self._alpha] \ + ) \ + if self._layer_color == self.theme_cls.transparentColor else \ + self._layer_color + RoundedRectangle: + group: "navigation-rail-rounded-rectangle" + radius: + ( \ + [ \ + ( \ + dp(16) \ + if self._navigation_rail.type != "unselected" else \ + dp(28) \ + ), \ + ] \ + ) \ + if self._navigation_rail else [0, ] + pos: + self.center_x - self._selected_region_width / 2, \ + ( \ + self.y \ + - ( \ + dp(4) \ + if self._navigation_rail.type != "unselected" else \ + dp(16) \ + ) \ + ) \ + if self._navigation_rail else 0 + size: + self._selected_region_width, \ + ( \ + self.height \ + + ( \ + dp(8) \ + if self._navigation_rail.type != "unselected" else \ + self.width + dp(8)\ + ) \ + ) \ + if self._navigation_rail else 0 + + pos_hint: {"center_x": .5} + + + + size_hint_y: None + height: 0 + halign: "center" + text_color: self.theme_cls.onSurfaceVariantColor + font_style: "Label" + role: "medium" + + + + size_hint: None, 1 + width: "80dp" + md_bg_color: self.theme_cls.surfaceColor + + BoxLayout: + id: box_items + orientation: "vertical" size_hint: None, None - size: root.size - - RippleWidget: - id: ripple_widget - size_hint: None, None - size: (container.width, container.width) - radius: container.width / 2 - scale_value_x: 0 - scale_value_y: 0 - scale_value_z: 0 - opacity: 0 - md_bg_color: - root.navigation_rail.ripple_color_item \ - if root.navigation_rail and \ - root.navigation_rail.ripple_color_item else \ - app.theme_cls.primary_color - - MDIcon: - id: icon - icon: root.icon - opposite_colors: root.opposite_colors - font_size: "24sp" - pos_hint: {"center_x": .5} - badge_icon: root.badge_icon - badge_font_size: root.badge_font_size - badge_icon_color: - root.badge_icon_color \ - if root.badge_icon_color else \ - (1, 1, 1, 1) - badge_bg_color: - root.badge_bg_color \ - if root.badge_bg_color else \ - app.theme_cls.error_color - theme_text_color: "Custom" - text_color: - ( \ - root.navigation_rail.icon_color_item_normal \ - if root.navigation_rail \ - and root.navigation_rail.icon_color_item_normal else \ - app.theme_cls.text_color \ - ) \ - if not root.active else \ - ( \ - root.navigation_rail.icon_color_item_active \ - if root.navigation_rail.icon_color_item_active else \ - app.theme_cls.text_color \ - ) - y: - container.height - \ - ( \ - (self.height + dp(4)) \ - if root.navigation_rail and \ - root.navigation_rail.type == "unselected" else \ - (self.height - dp(8)) \ - ) - - canvas.before: - Color: - rgba: - ( \ - ( \ - ( \ - app.theme_cls.primary_color \ - if not root.navigation_rail.selected_color_background else \ - root.navigation_rail.selected_color_background \ - ) \ - if root._release else \ - (0, 0, 0, 0) \ - ) \ - ) \ - if root.active else \ - (0, 0, 0, 0) - RoundedRectangle: - radius: - [root._selected_region_width / 2,] \ - if root.navigation_rail and \ - root.navigation_rail.type == "unselected" else \ - [root._selected_region_width / 4,] - size: - root._selected_region_width, \ - root._selected_region_width \ - if root.navigation_rail and \ - root.navigation_rail.type == "unselected" else \ - root._selected_region_width / 2 - pos: - self.center_x - self.width - dp(4), \ - self.center_y - root._selected_region_width / 2 \ - if root.navigation_rail and \ - root.navigation_rail.type == "unselected" else \ - self.center_y - root._selected_region_width / 4 - - MDLabel: - id: label - text: root.text - size_hint_x: None - text_size: None, root.height - adaptive_height: True - opposite_colors: root.opposite_colors - pos_hint: {"center_x": .5} - y: "16" - font_style: "Body2" - theme_text_color: "Custom" - font_name: - root.navigation_rail.font_name \ - if root.navigation_rail else \ - "Roboto" - text_color: - ( \ - root.navigation_rail.text_color_item_normal \ - if root.navigation_rail and \ - root.navigation_rail.text_color_item_normal else \ - app.theme_cls.text_color \ - ) \ - if not root.active else \ - ( \ - root.navigation_rail.text_color_item_active \ - if root.navigation_rail.text_color_item_active else \ - app.theme_cls.text_color \ - ) - opacity: - (0 if root.navigation_rail and \ - root.navigation_rail.type == "unselected" else 1) \ - if root.navigation_rail and \ - root.navigation_rail.type != "selected" else \ - (0 if not root.active else 1) + size: self.minimum_size + pos_hint: {"center_x": .5} + spacing: + { \ + "selected": "12dp", \ + "labeled": "24dp", \ + "unselected": "36dp", \ + }[root.type] diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.py index 762c74d..b4e7dad 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/navigationrail/navigationrail.py @@ -6,16 +6,19 @@ Components/NavigationRail .. seealso:: - `Material Design spec, Navigation rail `_ - -.. rubric:: Navigation rails provide access to primary destinations in apps - when using tablet and desktop screens. + `Material Design spec, Navigation rail `_ .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail.png :align: center -Usage ------ +.. rubric:: Navigation rails let people switch between UI views on mid-sized + devices. + +- Can contain 3-7 destinations plus an optional FAB +- Always put the rail in the same place, even on different screens of an app + +Example +------- .. tabs:: @@ -24,34 +27,54 @@ Usage .. code-block:: python from kivy.lang import Builder + from kivy.properties import StringProperty from kivymd.app import MDApp + from kivymd.uix.navigationrail import MDNavigationRailItem KV = ''' + + + MDNavigationRailItemIcon: + icon: root.icon + + MDNavigationRailItemLabel: + text: root.text + + MDBoxLayout: MDNavigationRail: + type: "selected" - MDNavigationRailItem: - text: "Python" - icon: "language-python" + MDNavigationRailMenuButton: + icon: "menu" - MDNavigationRailItem: - text: "JavaScript" - icon: "language-javascript" + MDNavigationRailFabButton: + icon: "home" - MDNavigationRailItem: - text: "CPP" - icon: "language-cpp" + CommonNavigationRailItem: + icon: "folder-outline" + text: "Files" - MDNavigationRailItem: - text: "Git" - icon: "git" + CommonNavigationRailItem: + icon: "bookmark-outline" + text: "Bookmark" + + CommonNavigationRailItem: + icon: "library-outline" + text: "Library" MDScreen: + md_bg_color: self.theme_cls.secondaryContainerColor ''' + class CommonNavigationRailItem(MDNavigationRailItem): + text = StringProperty() + icon = StringProperty() + + class Example(MDApp): def build(self): return Builder.load_string(KV) @@ -63,36 +86,64 @@ Usage .. code-block:: python + from kivy.clock import Clock + from kivy.properties import StringProperty + from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.navigationrail import MDNavigationRail, MDNavigationRailItem + from kivymd.uix.navigationrail import ( + MDNavigationRailItem, + MDNavigationRail, + MDNavigationRailMenuButton, + MDNavigationRailFabButton, + MDNavigationRailItemIcon, + MDNavigationRailItemLabel, + ) + from kivymd.uix.screen import MDScreen + + + class CommonNavigationRailItem(MDNavigationRailItem): + text = StringProperty() + icon = StringProperty() + + def on_icon(self, instance, value): + def on_icon(*ars): + self.add_widget(MDNavigationRailItemIcon(icon=value)) + Clock.schedule_once(on_icon) + + def on_text(self, instance, value): + def on_text(*ars): + self.add_widget(MDNavigationRailItemLabel(text=value)) + Clock.schedule_once(on_text) class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDBoxLayout( - MDNavigationRail( - MDNavigationRailItem( - text="Python", - icon="language-python", - ), - MDNavigationRailItem( - text="JavaScript", - icon="language-javascript", - ), - MDNavigationRailItem( - text="CPP", - icon="language-cpp", - ), - MDNavigationRailItem( - text="Git", - icon="git", - ), - ) - ) + return MDBoxLayout( + MDNavigationRail( + MDNavigationRailMenuButton( + icon="menu", + ), + MDNavigationRailFabButton( + icon="home", + ), + CommonNavigationRailItem( + icon="bookmark-outline", + text="Files", + ), + CommonNavigationRailItem( + icon="folder-outline", + text="Bookmark", + ), + CommonNavigationRailItem( + icon="library-outline", + text="Library", + ), + type="selected", + ), + MDScreen( + md_bg_color=self.theme_cls.secondaryContainerColor, + ), ) @@ -104,476 +155,220 @@ Usage Anatomy ------- +.. code-block:: kv + + MDNavigationRail: + + # Optional. + MDNavigationRailMenuButton: + icon: "menu" + + # Optional. + MDNavigationRailFabButton: + icon: "home" + + MDNavigationRailItem + + MDNavigationRailItemIcon: + icon: icon + + MDNavigationRailItemLabel: + text: text + + [...] + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anatomy.png :align: center -1. Container -2. Label text (optional) -3. Icon -4. Active indicator -5. Badge (optional) -6. Large badge (optional) -7. Large badge label (optional) -8. Menu icon (optional) +Anatomy item +------------ -Example -======= +.. code-block:: kv -.. tabs:: + MDNavigationRailItem - .. tab:: Declarative KV and imperative python styles + MDNavigationRailItemIcon: + icon: icon - .. code-block:: python + MDNavigationRailItemLabel: + text: text - from kivy.clock import Clock - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.behaviors import CommonElevationBehavior - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDFillRoundFlatIconButton - from kivymd.uix.label import MDLabel - from kivymd.uix.screen import MDScreen - - KV = ''' - #:import FadeTransition kivy.uix.screenmanager.FadeTransition - - - - elevation: 1 - shadow_radius: 12 - -height: "56dp" - - - - focus_color: "#e7e4c0" - unfocus_color: "#fffcf4" - - - MDScreen: - - MDNavigationLayout: - - ScreenManager: - - MDScreen: - - MDBoxLayout: - orientation: "vertical" - - MDBoxLayout: - adaptive_height: True - md_bg_color: "#fffcf4" - padding: "12dp" - - MDLabel: - text: "12:00" - adaptive_height: True - pos_hint: {"center_y": .5} - - MDBoxLayout: - - MDNavigationRail: - id: navigation_rail - md_bg_color: "#fffcf4" - selected_color_background: "#e7e4c0" - ripple_color_item: "#e7e4c0" - on_item_release: app.switch_screen(*args) - - MDNavigationRailMenuButton: - on_release: nav_drawer.set_state("open") - - MDNavigationRailFabButton: - md_bg_color: "#b0f0d6" - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - - MDNavigationRailItem: - text: "JavaScript" - icon: "language-javascript" - - MDNavigationRailItem: - text: "CPP" - icon: "language-cpp" - - MDNavigationRailItem: - text: "Swift" - icon: "language-swift" - - ScreenManager: - id: screen_manager - transition: - FadeTransition(duration=.2, clearcolor=app.theme_cls.bg_dark) - - MDNavigationDrawer: - id: nav_drawer - radius: 0, 16, 16, 0 - md_bg_color: "#fffcf4" - elevation: 2 - width: "240dp" - - MDNavigationDrawerMenu: - - MDBoxLayout: - orientation: "vertical" - adaptive_height: True - spacing: "12dp" - padding: 0, 0, 0, "12dp" - - MDIconButton: - icon: "menu" - - MDBoxLayout: - adaptive_height: True - padding: "12dp", 0, 0, 0 - - ExtendedButton: - text: "Compose" - icon: "pencil" - - DrawerClickableItem: - text: "Python" - icon: "language-python" - - DrawerClickableItem: - text: "JavaScript" - icon: "language-javascript" - - DrawerClickableItem: - text: "CPP" - icon: "language-cpp" - - DrawerClickableItem: - text: "Swift" - icon: "language-swift" - ''' - - - class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior): - ''' - Implements a button of type - `Extended FAB `_. - - .. rubric:: - Extended FABs help people take primary actions. - They're wider than FABs to accommodate a text label and larger target - area. - - This type of buttons is not yet implemented in the standard widget set - of the KivyMD library, so we will implement it ourselves in this class. - ''' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.padding = "16dp" - Clock.schedule_once(self.set_spacing) - - def set_spacing(self, interval): - self.ids.box.spacing = "12dp" - - def set_radius(self, *args): - if self.rounded_button: - value = self.height / 4 - self.radius = [value, value, value, value] - self._radius = value - - - class Example(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def switch_screen( - self, instance_navigation_rail, instance_navigation_rail_item - ): - ''' - Called when tapping on rail menu items. Switches application screens. - ''' - - self.root.ids.screen_manager.current = ( - instance_navigation_rail_item.icon.split("-")[1].lower() - ) - - def on_start(self): - '''Creates application screens.''' - - navigation_rail_items = self.root.ids.navigation_rail.get_items()[:] - navigation_rail_items.reverse() - - for widget in navigation_rail_items: - name_screen = widget.icon.split("-")[1].lower() - screen = MDScreen( - name=name_screen, - md_bg_color="#edd769", - radius=[18, 0, 0, 0], - ) - box = MDBoxLayout(padding="12dp") - label = MDLabel( - text=name_screen.capitalize(), - font_style="H1", - halign="right", - adaptive_height=True, - shorten=True, - ) - box.add_widget(label) - screen.add_widget(box) - self.root.ids.screen_manager.add_widget(screen) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivy.clock import Clock - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.uix.behaviors import CommonElevationBehavior - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDFillRoundFlatIconButton, MDIconButton - from kivymd.uix.label import MDLabel - from kivymd.uix.navigationdrawer import ( - MDNavigationDrawerItem, - MDNavigationLayout, - MDNavigationDrawer, - MDNavigationDrawerMenu, - ) - from kivymd.uix.navigationrail import ( - MDNavigationRail, - MDNavigationRailMenuButton, - MDNavigationRailFabButton, - MDNavigationRailItem, - ) - from kivymd.uix.screen import MDScreen - from kivymd.uix.screenmanager import MDScreenManager - - - class DrawerClickableItem(MDNavigationDrawerItem): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.focus_color = "#e7e4c0" - self.unfocus_color = self.theme_cls.bg_light - self.radius = 24 - - - class ExtendedButton(MDFillRoundFlatIconButton, CommonElevationBehavior): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.padding = "16dp" - self.elevation = 1 - self.shadow_radius = 12 - self.height = dp(56) - Clock.schedule_once(self.set_spacing) - - def set_spacing(self, interval): - self.ids.box.spacing = "12dp" - - def set_radius(self, *args): - if self.rounded_button: - self._radius = self.radius = self.height / 4 - - - class Example(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - self.theme_cls.primary_palette = "Orange" - return MDScreen( - MDNavigationLayout( - MDScreenManager( - MDScreen( - MDBoxLayout( - MDBoxLayout( - MDLabel( - text="12:00", - adaptive_height=True, - pos_hint={"center_y": 0.5}, - ), - adaptive_height=True, - md_bg_color="#fffcf4", - padding="12dp", - ), - MDBoxLayout( - MDNavigationRail( - MDNavigationRailMenuButton( - on_release=self.open_nav_drawer, - ), - MDNavigationRailFabButton( - md_bg_color="#b0f0d6", - ), - MDNavigationRailItem( - text="Python", - icon="language-python", - ), - MDNavigationRailItem( - text="JavaScript", - icon="language-javascript", - ), - MDNavigationRailItem( - text="CPP", - icon="language-cpp", - ), - MDNavigationRailItem( - text="Swift", - icon="language-swift", - ), - id="navigation_rail", - md_bg_color="#fffcf4", - selected_color_background="#e7e4c0", - ripple_color_item="#e7e4c0", - ), - MDScreenManager( - id="screen_manager_content", - ), - id="root_box", - ), - id="box_rail", - orientation="vertical", - ), - id="box", - ), - id="screen", - ), - id="screen_manager", - ), - MDNavigationDrawer( - MDNavigationDrawerMenu( - MDBoxLayout( - MDIconButton( - icon="menu", - ), - MDBoxLayout( - ExtendedButton( - text="Compose", - icon="pencil", - ), - adaptive_height=True, - padding=["12dp", 0, 0, 0], - ), - orientation="vertical", - adaptive_height=True, - spacing="12dp", - padding=("3dp", 0, 0, "12dp"), - ), - DrawerClickableItem( - text="Python", - icon="language-python", - ), - DrawerClickableItem( - text="JavaScript", - icon="language-javascript", - ), - DrawerClickableItem( - text="CPP", - icon="language-cpp", - ), - DrawerClickableItem( - text="Swift", - icon="language-swift", - ), - ), - id="nav_drawer", - radius=(0, 16, 16, 0), - elevation=4, - width="240dp", - ), - ) - - def switch_screen(self, *args, screen_manager_content=None): - ''' - Called when tapping on rail menu items. Switches application screens. - ''' - - instance_navigation_rail, instance_navigation_rail_item = args - screen_manager_content.current = ( - instance_navigation_rail_item.icon.split("-")[1].lower() - ) - - def open_nav_drawer(self, *args): - self.root.ids.nav_drawer.set_state("open") - - def on_start(self): - '''Creates application screens.''' - - screen_manager = self.root.ids.screen_manager - root_box = screen_manager.ids.screen.ids.box.ids.box_rail.ids.root_box - navigation_rail = root_box.ids.navigation_rail - screen_manager_content = root_box.ids.screen_manager_content - navigation_rail_items = navigation_rail.get_items()[:] - navigation_rail_items.reverse() - navigation_rail.bind( - on_item_release=lambda *args: self.switch_screen( - *args, screen_manager_content=screen_manager_content - ) - ) - - for widget in navigation_rail_items: - name_screen = widget.icon.split("-")[1].lower() - screen_manager_content.add_widget( - MDScreen( - MDBoxLayout( - MDLabel( - text=name_screen.capitalize(), - font_style="H1", - halign="right", - adaptive_height=True, - shorten=True, - ), - padding="12dp", - ), - name=name_screen, - md_bg_color="#edd769", - radius=[18, 0, 0, 0], - ), - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-example.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anatomy-item.png :align: center +Configurations +============== + +Rail types +---------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type.png + :align: center + +1. Selected +2. Unselected +3. Labeled + +Selected +-------- + +.. code-block:: kv + + MDNavigationRail: + type: "selected" # default + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-selected.gif + :align: center + +Unselected +---------- + +.. code-block:: kv + + MDNavigationRail: + type: "unselected" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-unselected.gif + :align: center + +Labeled +------- + +.. code-block:: kv + + MDNavigationRail: + type: "labeled" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-labeled.gif + :align: center + +Rail anchored +------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anchored.png + :align: center + +1. Top +2. Center +3. Bottom + +Top +--- + +.. code-block:: kv + + MDNavigationRail: + anchor: "top" + +Center +------ + +.. code-block:: kv + + MDNavigationRail: + anchor: "center" # default + +Bottom +------ + +.. code-block:: kv + + MDNavigationRail: + anchor: "bottom" + +API break +========= + +1.2.0 version +------------- + +.. code-block:: kv + + MDNavigationRail: + + MDNavigationRailMenuButton: + icon: "menu" + + MDNavigationRailFabButton: + icon: "home" + + MDNavigationRailItem: + icon: icon + text: text + + [...] + +2.2.0 version +------------- + +.. code-block:: kv + + MDNavigationRail: + + MDNavigationRailMenuButton: + icon: "menu" + + MDNavigationRailFabButton: + icon: "home" + + MDNavigationRailItem + + MDNavigationRailItemIcon: + icon: icon + + MDNavigationRailItemLabel: + text: text + + [...] """ __all__ = ( "MDNavigationRail", "MDNavigationRailItem", + "MDNavigationRailItemIcon", + "MDNavigationRailItemLabel", "MDNavigationRailFabButton", "MDNavigationRailMenuButton", ) import os -from typing import Union from kivy.animation import Animation from kivy.clock import Clock -from kivy.core.window import Window +from kivy.graphics import ( + StencilPush, + RoundedRectangle, + StencilUse, + Color, + Ellipse, + StencilUnUse, + StencilPop, +) from kivy.lang import Builder -from kivy.logger import Logger from kivy.metrics import dp from kivy.properties import ( BooleanProperty, ColorProperty, - ListProperty, NumericProperty, - ObjectProperty, OptionProperty, - StringProperty, VariableListProperty, + ObjectProperty, ) from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.relativelayout import RelativeLayout from kivymd import uix_path -from kivymd.uix.behaviors import ScaleBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDFloatingActionButton, MDIconButton -from kivymd.uix.card import MDCard -from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.widget import MDWidget +from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import ( + ScaleBehavior, + DeclarativeBehavior, + BackgroundColorBehavior, + RectangularRippleBehavior, +) +from kivymd.uix.behaviors.focus_behavior import FocusBehavior +from kivymd.uix.button import MDFabButton, MDIconButton +from kivymd.uix.label import MDIcon, MDLabel with open( os.path.join(uix_path, "navigationrail", "navigationrail.kv"), @@ -582,49 +377,22 @@ with open( Builder.load_string(kv_file.read()) -class PanelRoot(MDFloatLayout): +class MDNavigationRailFabButton(MDFabButton): """ - Contains - :class:`~MDNavigationRailFabButton`, :class:`~MDNavigationRailMenuButton` - buttons and a :class:`~Paneltems` container with menu items. - """ - - -class PanelItems(MDBoxLayout): - """Box for menu items.""" - - -class RippleWidget(MDWidget, ScaleBehavior): - """ - Implements a background color for a menu item - - (:class:`~MDNavigationRailItem`). - """ - - -class MDNavigationRailFabButton(MDFloatingActionButton): - """ - Implements an optional floating action button (FAB). + Implements a floating action button (FAB). For more information, see in the - :class:`~kivymd.uix.button.MDFloatingActionButton` class documentation. + :class:`~kivymd.uix.button.button.MDFabButton` + class documentation. """ - icon = StringProperty("pencil") + md_bg_color_disabled = ColorProperty(None) """ - Button icon name. + The background color in (r, g, b, a) or string format of the switch when + the widget is disabled. - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailFabButton: - icon: "home" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-fab-button-icon.png - :align: center - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'pencil'`. + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ @@ -633,166 +401,145 @@ class MDNavigationRailMenuButton(MDIconButton): Implements a menu button. For more information, see in the - :class:`~kivymd.uix.button.MDIconButton` classes documentation. + :class:`~kivymd.uix.button.button.MDIconButton` class documentation. """ - icon = StringProperty("menu") + md_bg_color_disabled = ColorProperty(None) """ - Button icon name. + The background color in (r, g, b, a) or string format of the switch when + the widget is disabled. - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailMenuButton: - icon: "home" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-menu-button-icon.png - :align: center - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'menu'`. + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ -class MDNavigationRailItem(ButtonBehavior, MDBoxLayout): +class MDNavigationRailItemIcon(RectangularRippleBehavior, MDIcon): + """ + Implements an icon for the :class:`~MDNavigationRailItem` class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivymd.uix.label.label.MDIcon` + classes documentation. + + .. versionchanged:: 2.0.0 + """ + + active_indicator_color = ColorProperty(None) + """ + Background color of the active indicator in (r, g, b, a) or string format. + + :attr:`active_indicator_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + _alpha = NumericProperty(0) + _active = BooleanProperty(False) + _navigation_rail = ObjectProperty() + _navigation_item = ObjectProperty() + _layer_color = ColorProperty([0, 0, 0, 0]) + _selected_region_width = NumericProperty(dp(0)) + + def anim_complete(self, *args): + super().anim_complete() + self._navigation_rail.set_active_item(self._navigation_item) + + def lay_canvas_instructions(self) -> None: + if not self.ripple_effect: + return + + canvas_rectangle = self.canvas.before.get_group( + "navigation-rail-rounded-rectangle" + )[0] + + with self.canvas.after if self.ripple_canvas_after else self.canvas.before: + if hasattr(self, "radius"): + self.radius = [ + canvas_rectangle.radius[0][0], + ] + self._round_rad = self.radius + StencilPush(group="rectangular_ripple_behavior") + RoundedRectangle( + pos=canvas_rectangle.pos, + size=canvas_rectangle.size, + radius=self._round_rad, + group="rectangular_ripple_behavior", + ) + StencilUse(group="rectangular_ripple_behavior") + self.col_instruction = Color( + rgba=self.ripple_color, group="rectangular_ripple_behavior" + ) + self.ellipse = Ellipse( + size=(self._ripple_rad, self._ripple_rad), + pos=( + self.ripple_pos[0] - self._ripple_rad / 2.0, + self.ripple_pos[1] - self._ripple_rad / 2.0, + ), + group="rectangular_ripple_behavior", + ) + StencilUnUse(group="rectangular_ripple_behavior") + RoundedRectangle( + pos=self.pos, + size=self.size, + radius=self._round_rad, + group="rectangular_ripple_behavior", + ) + StencilPop(group="rectangular_ripple_behavior") + self.bind(ripple_color=self._set_color, _ripple_rad=self._set_ellipse) + + +class MDNavigationRailItemLabel(ScaleBehavior, MDLabel): + """ + Implements an label for the :class:`~MDNavigationRailItem` class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivymd.uix.label.label.MDLabel` + classes documentation. + + .. versionchanged:: 2.0.0 + """ + + scale_value_y = NumericProperty(0) + """ + Y-axis value. + + :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0`. + """ + + _active = BooleanProperty(False) + + def on__active(self, instance, value) -> None: + """Fired when the :attr:`_active` value changes.""" + + self.text_color = ( + self.theme_cls.onSurfaceColor + if value + else self.theme_cls.onSurfaceVariantColor + ) + + +class MDNavigationRailItem( + DeclarativeBehavior, + ButtonBehavior, + ThemableBehavior, + FocusBehavior, + BoxLayout, +): """ Implements a menu item with an icon and text. For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.boxlayout.MDBoxLayout` + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.focus_behavior.FocusBehavior` + :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation. """ - navigation_rail = ObjectProperty() - """ - :class:`~MDNavigationRail` object. - - :attr:`navigation_rail` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - icon = StringProperty("checkbox-blank-circle") - """ - Icon item. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - icon: "language-python" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-icon.png - :align: center - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'checkbox-blank'`. - """ - - text = StringProperty() - """ - Text item. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-text.png - :align: center - - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - badge_icon = StringProperty() - """ - Badge icon name. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - badge_icon: "plus" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-icon.png - :align: center - - :attr:`badge_icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - badge_icon_color = ColorProperty(None) - """ - Badge icon color in (r, g, b, a) format. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - badge_icon: "plus" - badge_icon_color: 0, 0, 1, 1 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-icon-color.png - :align: center - - :attr:`badge_icon_color` is an :class:`~kivy.properties.StringProperty` - and defaults to `None`. - """ - - badge_bg_color = ColorProperty(None) - """ - Badge icon background color in (r, g, b, a) format. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - badge_icon: "plus" - badge_bg_color: "#b0f0d6" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-bg-color.png - :align: center - - :attr:`badge_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - badge_font_size = NumericProperty(0) - """ - Badge icon font size. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - badge_icon: "plus" - badge_font_size: "24sp" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-item-badge-font-size.png - :align: center - - :attr:`badge_font_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - active = BooleanProperty(False) """ Is the element active. @@ -801,60 +548,73 @@ class MDNavigationRailItem(ButtonBehavior, MDBoxLayout): and defaults to `False`. """ - _selected_region_width = NumericProperty("56dp") - _ripple_size = ListProperty([0, 0]) - _release = BooleanProperty(False) + radius = VariableListProperty(0, length=4) + """ + Item radius. - def on_active( - self, instance_navigation_rail_item, value_active: bool - ) -> None: - """Called when the value of `active` changes.""" + .. versionchanged:: 2.0.0 - self.animation_size_ripple_area(1 if value_active else 0) + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ - def animation_size_ripple_area(self, value: int) -> None: - """Animates the size/fade of the ripple area.""" + _navigation_rail = ObjectProperty() + _icon_item = ObjectProperty() - Animation( - scale_value_x=value, - scale_value_y=value, - scale_value_z=value, - opacity=value, - d=0.25, - t=self.navigation_rail.ripple_transition, - ).start(self.ids.ripple_widget) + def on_active(self, instance, value) -> None: + """Fired when the :attr:`active` value changes.""" - def on_press(self) -> None: - """Called when pressed on a panel element.""" + if value: + Clock.schedule_once(self.on_leave) - self._release = False - self.active = True - self.navigation_rail.deselect_item(self) - self.navigation_rail.dispatch("on_item_press", self) + def on_enter(self, *args) -> None: + """Fired when mouse enter the bbox of the widget.""" - def on_release(self) -> None: - """Called when released on a panel element.""" + if not self.active: + # FIXME: Move layer creation to + # kivymd/uix/behaviors/state_layer_behavior.py module. + self._icon_item._layer_color = self.theme_cls.onSurfaceColor[ + :-1 + ] + [0.12] + Animation( + _selected_region_width=self._icon_item.width + dp(32), + d=0.2, + ).start(self._icon_item) - self._release = True - self.animation_size_ripple_area(0) - self.navigation_rail.dispatch("on_item_release", self) + def on_leave(self, *args) -> None: + """Fired when the mouse goes outside the widget border.""" + + self._icon_item._layer_color = self.theme_cls.transparentColor + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDNavigationRailItemLabel): + Clock.schedule_once(lambda x: self._check_type_rail(widget)) + elif isinstance(widget, MDNavigationRailItemIcon): + widget._navigation_rail = self._navigation_rail + widget._navigation_item = self + self._icon_item = widget + return super().add_widget(widget) + + def _check_type_rail(self, instance: MDNavigationRailItemLabel): + if self._navigation_rail.type == "labeled": + instance.scale_value_y = 1 -class MDNavigationRail(MDCard): +class MDNavigationRail( + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + RelativeLayout, +): """ Navigation rail class. For more information, see in the - :class:`~kivymd.uix.card.MDCard` class documentation. - - :Events: - :attr:`on_item_press` - Called on the `on_press` event of menu item - - :class:`~MDNavigationRailItem`. - - :attr:`on_item_release` - Called on the `on_release` event of menu item - - :class:`~MDNavigationRailItem`. + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.relativelayout.RelativeLayout` + classes documentation. """ radius = VariableListProperty(0, length=4) @@ -865,417 +625,145 @@ class MDNavigationRail(MDCard): and defaults to `[0, 0, 0, 0]`. """ - padding = VariableListProperty([0, "36dp", 0, "36dp"], length=4) - """ - Padding between layout box and children: - [padding_left, padding_top, padding_right, padding_bottom]. - - :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, '36dp', 0, '36dp']`. - """ - - anchor = OptionProperty("top", options=["top", "bottom", "center"]) + anchor = OptionProperty("center", options=["top", "bottom", "center"]) """ The position of the panel with menu items. Available options are: `'top'`, `'bottom'`, `'center'`. - .. rubric:: Top - - .. code-block:: kv - - MDNavigationRail: - anchor: "top" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-anchor-top.png - :align: center - - .. rubric:: Center - - .. code-block:: kv - - MDNavigationRail: - anchor: "center" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-center.png - :align: center - - .. rubric:: Bottom - - .. code-block:: kv - - MDNavigationRail: - anchor: "bottom" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-bottom.png - :align: center - :attr:`anchor` is an :class:`~kivy.properties.OptionProperty` and defaults to `'top'`. """ type = OptionProperty( - "labeled", options=["labeled", "selected", "unselected"] + "selected", options=["labeled", "selected", "unselected"] ) """ Type of switching menu items. Available options are: `'labeled'`, `'selected'`, `'unselected'`. - .. rubric:: Labeled - - .. code-block:: kv - - MDNavigationRail: - type: "labeled" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-labeled.png - :align: center - - .. rubric:: Selected - - .. code-block:: kv - - MDNavigationRail: - type: "selected" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-selected.gif - :align: center - - .. rubric:: Unselected - - .. code-block:: kv - - MDNavigationRail: - type: "unselected" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-type-unselected.gif - :align: center - :attr:`type` is an :class:`~kivy.properties.OptionProperty` and defaults to `'labeled'`. """ - text_color_item_normal = ColorProperty(None) - """ - The text color in (r, g, b, a) or string format of the normal menu item - (:class:`~MDNavigationRailItem`). - - .. code-block:: kv - - MDNavigationRail: - text_color_item_normal: app.theme_cls.primary_color - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-text-color-item-normal.png - :align: center - - :attr:`text_color_item_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_color_item_active = ColorProperty(None) - """ - The text color in (r, g, b, a) or string format of the active menu item - (:class:`~MDNavigationRailItem`). - - .. code-block:: kv - - MDNavigationRail: - text_color_item_active: app.theme_cls.primary_color - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-text-color-item-active.png - :align: center - - :attr:`text_color_item_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_color_item_normal = ColorProperty(None) - """ - The icon color in (r, g, b, a) or string format of the normal menu item - (:class:`~MDNavigationRailItem`). - - .. code-block:: kv - - MDNavigationRail: - icon_color_item_normal: app.theme_cls.primary_color - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-icon-color-item-normal.png - :align: center - - :attr:`icon_color_item_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - icon_color_item_active = ColorProperty(None) - """ - The icon color in (r, g, b, a) or string format of the active menu item - (:class:`~MDNavigationRailItem`). - - .. code-block:: kv - - MDNavigationRail: - icon_color_item_active: app.theme_cls.primary_color - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-icon-color-item-active.png - :align: center - - :attr:`icon_color_item_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - selected_color_background = ColorProperty(None) - """ - Background color which will highlight the icon of the active menu item - - :class:`~MDNavigationRailItem` - in (r, g, b, a) format. - - .. code-block:: kv - - MDNavigationRail: - selected_color_background: "#e7e4c0" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-selected-color-background.png - :align: center - - :attr:`selected_color_background` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - ripple_color_item = ColorProperty(None) - """ - Ripple effect color of menu items (:class:`~MDNavigationRailItem`) - in (r, g, b, a) format. - - .. code-block:: kv - - MDNavigationRail: - ripple_color_item: "#e7e4c0" - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-ripple-color-item.png - :align: center - - :attr:`ripple_color_item` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - ripple_transition = StringProperty("out_cubic") - """ - Type of animation of the ripple effect when a menu item is selected. - - :attr:`ripple_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'ripple_transition'`. - """ - - current_selected_item = NumericProperty(0) - """ - Index of the menu list item (:class:`~MDNavigationRailItem`) that will be - active by default - - .. code-block:: kv - - MDNavigationRail: - current_selected_item: 1 - - MDNavigationRailItem: - ... - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-current-selected-item.png - :align: center - - :attr:`current_selected_item` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - font_name = StringProperty("Roboto") - """ - Font path for menu item (:class:`~MDNavigationRailItem`) text. - - .. code-block:: kv - - MDNavigationRail: - - MDNavigationRailItem: - text: "Python" - icon: "language-python" - font_name: "nasalization-rg.ttf" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/navigation-rail-font-name.png - :align: center - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ + fab_button: MDNavigationRailFabButton = ObjectProperty() + menu_button: MDNavigationRailFabButton = ObjectProperty() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - Clock.schedule_once(self.set_pos_menu_fab_buttons) - Clock.schedule_once(self.set_current_selected_item) - self.register_event_type("on_item_press") - self.register_event_type("on_item_release") + Clock.schedule_once(self._check_anchor) - def on_size(self, *args): - Clock.schedule_once(self.set_pos_menu_fab_buttons) + def on_size(self, *args) -> None: + """Fired when the application screen size changes.""" - def on_item_press(self, *args) -> None: - """ - Called on the `on_press` event of menu item - - :class:`~MDNavigationRailItem`. - """ - - def on_item_release(self, *args) -> None: - """ - Called on the `on_release` event of menu item - - :class:`~MDNavigationRailItem`. - """ - - def deselect_item( - self, selected_navigation_rail_item: MDNavigationRailItem - ) -> None: - """ - Sets the `active` value to `False` for all menu items - (:class:`~MDNavigationRailItem`) except the selected item. - Called when a menu item is touched. - """ - - for navigation_rail_item in self.ids.box_items.children: - if selected_navigation_rail_item is not navigation_rail_item: - navigation_rail_item.active = False + Clock.schedule_once(self._set_pos_menu_fab_buttons) + Clock.schedule_once(self._check_anchor) def get_items(self) -> list: - """Returns a list of :class:`~MDNavigationRailItem` objects""" + """Returns a list of :class:`~MDNavigationRailItem` objects.""" return self.ids.box_items.children - def set_pos_panel_items( - self, - instance_fab_button: Union[None, MDNavigationRailFabButton], - instance_menu_button: Union[None, MDNavigationRailFabButton], - ) -> None: - """Set :class:`~Paneltems` panel position with menu items.""" + def set_active_item(self, item: MDNavigationRailItem) -> None: + """Sets the active menu list item.""" - if self.anchor == "top": - if instance_fab_button: - self.ids.box_items.y = instance_fab_button.y - ( - len(self.ids.box_items.children) * dp(56) - + self.padding[1] * 2 - + dp(24) - ) + for widget in self.ids.box_items.children: + if item is widget: + widget.active = not widget.active + + for widget_item in item.children: + if isinstance(widget_item, MDNavigationRailItemLabel): + widget_item._active = widget.active + if self.type == "selected": + Animation( + scale_value_y=1 if widget.active else 0, + height=widget_item.texture_size[1] + if widget.active + else 0, + d=0.2, + ).start(widget_item) + if isinstance(widget_item, MDNavigationRailItemIcon): + widget_item._active = widget.active + widget_item._alpha = 1 if widget.active else 0 + widget_item._selected_region_width = 0 + Animation( + _selected_region_width=widget_item.width + dp(32) + if widget.active + else 0, + d=0.2, + ).start(widget_item) else: - if not instance_menu_button: - self.ids.box_items.pos_hint = {"top": 1} - else: - self.ids.box_items.y = instance_menu_button.y - ( - len(self.ids.box_items.children) * dp(56) - + self.padding[1] * 2 - ) - elif self.anchor == "center": - self.ids.box_items.pos_hint = {"center_y": 0.5} - elif self.anchor == "bottom": - self.ids.box_items.y = dp(12) - - def set_current_selected_item(self, interval: Union[int, float]) -> None: - """Sets the active menu list item (:class:`~MDNavigationRailItem`).""" - - if self.ids.box_items.children: - items = self.ids.box_items.children[:] - items.reverse() - - if len(items) <= self.current_selected_item: - Logger.error( - f"MDNavigationRail:You have " - f"{len(self.ids.box_items.children)} menu items, but you " - f"set {self.current_selected_item} as the active item. " - f"The very first menu item will be set active." - ) - index = 0 - else: - index = self.current_selected_item - - items[index].dispatch("on_press") - items[index].dispatch("on_release") - - def set_pos_menu_fab_buttons(self, *args) -> None: - """ - Sets the position of the :class:`~MDNavigationRailFabButton` and - :class:`~MDNavigationRailMenuButton` buttons on the panel. - """ - - fab_button = None # MDNavigationRailFabButton - menu_button = None # MDNavigationRailMenuButton - - for widget in self.ids.box_buttons.children: - if isinstance(widget, MDNavigationRailFabButton): - fab_button = widget - if isinstance(widget, MDNavigationRailMenuButton): - menu_button = widget - - if fab_button and menu_button: - - def set_fab_button_y(interval): - fab_button.y = self.parent.height - ( - menu_button.height - + fab_button.height - + self.padding[1] - + dp(18) - ) - self.set_pos_panel_items(fab_button, menu_button) - - Clock.schedule_once(set_fab_button_y) - elif fab_button and not menu_button: - - def set_fab_button_y(interval): - fab_button.y = self.parent.height - ( - self.padding[1] + fab_button.height - ) - self.set_pos_panel_items(fab_button, menu_button) - - Clock.schedule_once(set_fab_button_y) - else: - Clock.schedule_once( - lambda x: self.set_pos_panel_items(fab_button, menu_button) - ) + widget.active = False + for widget_item in widget.children: + widget_item._active = widget.active + if isinstance(widget_item, MDNavigationRailItemLabel): + if self.type == "selected": + Animation(scale_value_y=0, height=0, d=0.2).start( + widget_item + ) + if isinstance(widget_item, MDNavigationRailItemIcon): + Animation( + _selected_region_width=0, + _alpha=0, + d=0.2, + ).start(widget_item) def add_widget(self, widget, *args, **kwargs): if isinstance(widget, MDNavigationRailFabButton): - self.ids.box_buttons.add_widget(widget) + self.fab_button = widget + super().add_widget(widget) elif isinstance(widget, MDNavigationRailMenuButton): - self.ids.box_buttons.add_widget(widget) + self.menu_button = widget + super().add_widget(widget) elif isinstance(widget, MDNavigationRailItem): - widget.navigation_rail = self self.ids.box_items.add_widget(widget) - elif isinstance(widget, (PanelRoot, PanelItems)): + widget._navigation_rail = self + widget._navigation_item = widget + widget.bind(on_release=self.set_active_item) + else: return super().add_widget(widget) + + def _set_pos_menu_fab_buttons(self, *args): + def set_pos_menu_fab_buttons(*args): + if self.fab_button and not self.menu_button: + self.fab_button.y = self.height - ( + self.fab_button.height + dp(48) + ) + elif self.menu_button and not self.fab_button: + self.menu_button.y = self.height - ( + self.menu_button.height + dp(38) + ) + elif self.fab_button and self.menu_button: + self.menu_button.y = self.height - ( + self.menu_button.height + dp(38) + ) + self.fab_button.y = self.height - ( + self.fab_button.height + dp(48) + dp(48) + ) + + Clock.schedule_once(set_pos_menu_fab_buttons) + + def _check_anchor(self, *args): + def set_top_pos(*args): + anchor_button = None + if ( + self.fab_button + and not self.menu_button + or self.fab_button + and self.menu_button + ): + anchor_button = self.fab_button + elif self.menu_button and not self.fab_button: + anchor_button = self.menu_button + + self.ids.box_items.y = ( + anchor_button.y + - (len(self.ids.box_items.children) * dp(56)) + - dp(56) + ) + + if self.anchor == "center": + self.ids.box_items.pos_hint = {"center_y": 0.5} + elif self.anchor == "top": + Clock.schedule_once(set_top_pos) + elif self.anchor == "bottom": + self.ids.box_items.y = dp(56) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__init__.py index d8039aa..6fe040f 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__init__.py @@ -1,3 +1,10 @@ -from .colorpicker import MDColorPicker # NOQA F401 -from .datepicker import MDDatePicker # NOQA F401 -from .timepicker import MDTimePicker # NOQA F401 +from .datepicker import ( + MDModalDatePicker, + MDDockedDatePicker, + MDModalInputDatePicker, +) # NOQA F401 +from .timepicker import ( + MDTimePickerDialVertical, + MDTimePickerDialHorizontal, + MDTimePickerInput, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__pycache__/__init__.cpython-311.pyc index fa8b5e1..73ab6a6 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__init__.py deleted file mode 100644 index 5d5f5dd..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .colorpicker import MDColorPicker # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 3df075c..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__pycache__/colorpicker.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__pycache__/colorpicker.cpython-311.pyc deleted file mode 100644 index b341274..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/__pycache__/colorpicker.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/colorpicker.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/colorpicker.kv deleted file mode 100644 index dde7c0f..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/colorpicker.kv +++ /dev/null @@ -1,299 +0,0 @@ -#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT -#:import images_path kivymd.images_path -#:import colors kivymd.color_definitions.colors -#:import Window kivy.core.window.Window - - - - orientation: "vertical" - adaptive_height: True - spacing: "12dp" - padding: 0, 0, 0, "8dp" - - FitImage: - size_hint_y: None - height: "36dp" - source: f"{images_path}/alpha_layer.png" - radius: [8,] - - canvas.after: - Color: - rgba: - root._rgb[:-1] + [root._opacity_value_selected_color] - RoundedRectangle: - pos: self.pos - size: self.size - radius: [8,] - - MDSlider: - id: slider - size_hint_y: None - height: "12dp" - hint: False - max: 1 - value: root._opacity_value_selected_color - on_value: - root._opacity_value_selected_color = self.value - if root.color_picker: \ - root.color_picker._opacity_value_selected_color = self.value - - - - spacing: "12dp" - color_slider: "Red" - max: 255 - adaptive_height: True - - MDSlider: - id: slider - size_hint_y: None - height: "36dp" - color: colors[root.color_slider]["500"] - max: root.max - value: 1 if root.max == 1 else 0 - on_value: - root.parent.dispatch("on_slide_value", root.parent.get_color()) - - MDLabel: - adaptive_size: True - -text_size: None, None - pos_hint: {"center_y": .5} - text: - str(int(slider.value)) \ - if root.max != 1 \ - else str(round(slider.value, 1)) - - - - orientation: "vertical" - padding: "12dp", "24dp", "12dp", 0 - spacing: "24dp" - - SliderItem: - id: slider_red - color_slider: "Red" - - SliderItem: - id: slider_green - color_slider: "Green" - - SliderItem: - id: slider_blue - color_slider: "Blue" - - Widget: - - SelectAlphaChannelWidget: - id: select_alpha_channel_widget - color_picker: root.color_picker - - - - orientation: "vertical" - padding: "12dp", "12dp", "12dp", 0 - spacing: "8dp" - - MDBoxLayout: - id: color_selection_box - spacing: "12dp" - - Widget: - id: gradient_widget - - MDBoxLayout: - orientation: "vertical" - size_hint_x: None - width: "24dp" - - canvas.before: - StencilPush - RoundedRectangle: - size: self.size - pos: self.pos - radius: root.color_picker.radius_color_scale - StencilUse - - canvas.after: - StencilUnUse - RoundedRectangle: - size: self.size - pos: self.pos - radius: root.color_picker.radius_color_scale - StencilPop - - Image: - source: f"{images_path}/blue.png" - allow_stretch: True - keep_ratio: False - on_touch_down: - if self.collide_point(*args[1].pos): \ - root.updated_canvas(self, args[1]) - - Image: - source: f"{images_path}/green.png" - allow_stretch: True - keep_ratio: False - on_touch_down: - if self.collide_point(*args[1].pos): \ - root.updated_canvas(self, args[1]) - - Image: - source: f"{images_path}/yellow.png" - allow_stretch: True - keep_ratio: False - on_touch_down: - if self.collide_point(*args[1].pos): \ - root.updated_canvas(self, args[1]) - - Image: - source: f"{images_path}/red.png" - allow_stretch: True - keep_ratio: False - on_touch_down: - if self.collide_point(*args[1].pos): \ - root.updated_canvas(self, args[1]) - - Image: - source: f"{images_path}/black.png" - allow_stretch: True - keep_ratio: False - on_touch_down: - if self.collide_point(*args[1].pos): \ - root.updated_canvas(self, args[1]) - - SelectAlphaChannelWidget: - id: select_alpha_channel_widget - color_picker: root.color_picker - - - - rv: rv - - RecycleView: - id: rv - key_viewclass: "viewclass" - key_size: "height" - - RecycleBoxLayout: - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - padding: "8dp" - spacing: "8dp" - default_size_hint: 1, None - default_size: None, dp(48) - - - - size_hint_y: None - padding: "12dp" - md_bg_color: root.color - radius: [8,] - - MDLabel: - text: root.hue_code - theme_text_color: "Custom" - text_color: root.text_color - halign: "center" - - - - # These are the sums of the widths of the `TypeColorButton` buttons in the - # `type_color_button_box` box. - size_hint_min_x: dp(264) - - MDBoxLayout: - orientation: "vertical" - - MDBoxLayout: - id: header - orientation: "vertical" - padding: 0, "8dp", 0, 0 - spacing: "8dp" - radius: root.radius[:2] + [0, 0] - size_hint_y: None - height: STANDARD_INCREMENT - md_bg_color: - app.theme_cls.primary_color \ - if not root.default_color \ - else root.default_color - - MDLabel: - id: lbl_color_value - halign: "center" - shorten: True - bold: True - markup: True - - MDBoxLayout: - id: type_color_button_box - adaptive_height: True - - TypeColorButton: - text: "HEX" - group: "x" - size_hint_x: 1 - on_release: root.type_color = self.text - - TypeColorButton: - text: "RGB" - group: "x" - size_hint_x: 1 - on_release: root.type_color = self.text - - TypeColorButton: - text: "RGBA" - group: "x" - size_hint_x: 1 - on_release: root.type_color = self.text - - MDBottomNavigation: - id: bottom_navigation - use_text: False - on_switch_tabs: root.dispatch("on_switch_tabs", *args) - - MDBottomNavigationItem: - id: bottom_navigation_gradient - name: "bottom navigation gradient" - icon: "gradient-vertical" - - MDBottomNavigationItem: - id: view_headline - name: "view headline" - icon: "view-headline" - - ColorListTab: - id: color_list_tabs - text_color_normal: 0, 0, 0, 1 - on_tab_switch: self.generates_list_colors(*args) - color_picker: root - - MDBottomNavigationItem: - id: tune - name: "tune" - icon: "tune" - - SliderTab: - color_picker: root - on_slide_value: - root.dispatch("on_select_color", args[1]) - - MDBoxLayout: - size_hint_y: None - height: "48dp" - md_bg_color: app.theme_cls.bg_dark - radius: [0, 0] + root.radius[2:] - - MDFlatButton: - text: root.text_button_ok - size_hint: 1, 1 - on_release: - root.dispatch( \ - "on_release", \ - root.type_color, \ - root._get_selected_color(root.selected_color)) - - MDFlatButton: - text: root.text_button_cancel - size_hint: 1, 1 - on_release: root.dismiss() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/colorpicker.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/colorpicker.py deleted file mode 100644 index 4dfdfd1..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/colorpicker/colorpicker.py +++ /dev/null @@ -1,657 +0,0 @@ -""" -Components/ColorPicker -====================== - -.. versionadded:: 1.0.0 - -.. rubric:: Create, share, and apply color palettes to your UI, as well as measure the accessibility level of any color combination.. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/color-picker-preview.png - :align: center - -Usage ------ - -.. code-block:: python - - from typing import Union - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.pickers import MDColorPicker - - KV = ''' - MDScreen: - - MDTopAppBar: - id: toolbar - title: "MDTopAppBar" - pos_hint: {"top": 1} - - MDRaisedButton: - text: "OPEN PICKER" - pos_hint: {"center_x": .5, "center_y": .5} - md_bg_color: toolbar.md_bg_color - on_release: app.open_color_picker() - ''' - - - class MyApp(MDApp): - def build(self): - return Builder.load_string(KV) - - def open_color_picker(self): - color_picker = MDColorPicker(size_hint=(0.45, 0.85)) - color_picker.open() - color_picker.bind( - on_select_color=self.on_select_color, - on_release=self.get_selected_color, - ) - - def update_color(self, color: list) -> None: - self.root.ids.toolbar.md_bg_color = color - - def get_selected_color( - self, - instance_color_picker: MDColorPicker, - type_color: str, - selected_color: Union[list, str], - ): - '''Return selected color.''' - - print(f"Selected color is {selected_color}") - self.update_color(selected_color[:-1] + [1]) - - def on_select_color(self, instance_gradient_tab, color: list) -> None: - '''Called when a gradient image is clicked.''' - - - MyApp().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/color-picker-usage.png - :align: center -""" - -import os -import struct -from io import BytesIO -from typing import List, Union - -from kivy.clock import Clock -from kivy.core.image import Image as CoreImage -from kivy.graphics import RoundedRectangle -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, - VariableListProperty, -) -from kivy.uix.behaviors import ButtonBehavior -from kivy.utils import get_color_from_hex, get_hex_from_color -from PIL import Image as PilImage -from PIL import ImageDraw - -from kivymd import uix_path -from kivymd.color_definitions import colors as _colors -from kivymd.color_definitions import text_colors -from kivymd.uix.behaviors import RectangularRippleBehavior -from kivymd.uix.behaviors.toggle_behavior import MDToggleButton -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.dialog import BaseDialog -from kivymd.uix.tab import MDTabs, MDTabsBase, MDTabsLabel - -__all__ = ("MDColorPicker",) - -with open( - os.path.join(uix_path, "pickers", "colorpicker", "colorpicker.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) - - -class TypeColorButton(MDRaisedButton, MDToggleButton): - """ - The class implements the button to switch the color type - - 'RGBA', 'HEX', 'RGB'. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.theme_text_color = "Custom" - self.text_color = (0, 0, 0, 1) - self.elevation = 0 - - -class SelectAlphaChannelWidget(MDBoxLayout): - """ - The class implements the widget with the current color and slider to set - the value of the transparency of the selected color. - """ - - # :class:`~kivymd.uix.colorpicker.MDColorPicker` class. - color_picker = ObjectProperty() - - # The `RGB` value for the transparency preview widget of the selected - # color. - _rgb = ColorProperty([0, 0, 0, 0]) - # The opacity value for the transparency preview widget of the selected - # color. - _opacity_value_selected_color = NumericProperty(1) - - def on_color_picker( - self, instance_select_alpha_channel_widget, instance_color_picker - ) -> None: - instance_color_picker.bind(_rgb=self.set_scale_rgb) - - def set_scale_rgb( - self, - instance_color_picker, - color: Union[List[int], List[float]], - ) -> None: - if color[0] > 1: - self._rgb = [x / 255.0 for x in color] - else: - self._rgb = color - - -class SliderTab(MDBoxLayout): - """ - The class has implemented `RGB` value sliders and a scale for setting the - transparency value of the selected color. This is the third tab on the - bottom navigation panel. - """ - - # :class:`~kivymd.uix.colorpicker.MDColorPicker` class. - color_picker = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_slide_value") - - def get_color(self) -> List[float]: - return [ - self.ids.slider_red.ids.slider.value / 255, - self.ids.slider_green.ids.slider.value / 255, - self.ids.slider_blue.ids.slider.value / 255, - self.color_picker._opacity_value_selected_color, - ] - - def on_slide_value(self, *args) -> None: - """Basic event handler for changing the slider value.""" - - -class GradientTab(MDBoxLayout): - """ - The class implements a tab with a gradient, a color selection scale and - a scale for setting the transparency value of the selected color. - This is the first tab on the bottom navigation panel. - """ - - # :class:`~kivymd.uix.colorpicker.MDColorPicker` class. - color_picker = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.rectangle = None - self.texture = None - Clock.schedule_once(lambda x: self.create_gradient_texture()) - Clock.schedule_once(self.create_canvas_with_gradient_texture) - - def create_gradient_texture( - self, r_g_b=None, interval: Union[int, float] = 0 - ) -> None: - """ - Creates a gradient value buffer and texture object. - Called when clicking on the gradient bar to the right. - """ - - # TODO: Perhaps there is a better way to create a gradient. - # The implementation using the PIL package is most likely not the most - # better. In any case, performance tests should be carried out. - gradient_widget_width = int(self.ids.gradient_widget.width) - gradient_widget_height = int(self.ids.gradient_widget.height - dp(100)) - img = PilImage.new( - "RGBA", (gradient_widget_width, gradient_widget_height), "#FFFFFF" - ) - draw = ImageDraw.Draw(img) - - if not self.color_picker.default_color: - r, g, b = ( - r_g_b - if r_g_b - else self.color_picker.get_rgb(self.theme_cls.primary_color) - ) - else: - r, g, b = [ - int(value * 255) - for value in self.color_picker.default_color[:-1] - ] - self.color_picker._rgb = [r, g, b] - ( - r_adjacent_color_constant, - g_adjacent_color_constant, - b_adjacent_color_constant, - ) = ( - self.color_picker.adjacent_color_constants - if r_g_b != (0, 0, 0) - else (0.40, 0.40, 0.40) # if the selected color is black - ) - - for i in range(gradient_widget_width): - r, g, b = ( - r + r_adjacent_color_constant, - g + g_adjacent_color_constant, - b + b_adjacent_color_constant, - ) - draw.line( - (i, 0, i, gradient_widget_width), fill=(int(r), int(g), int(b)) - ) - - data = BytesIO() - img.save(data, format="png") - data.seek(0) - self.texture = CoreImage(BytesIO(data.read()), ext="png").texture - - def create_canvas_with_gradient_texture( - self, interval: Union[int, float] - ) -> None: - """Creates a canvas with a gradient texture.""" - - with self.ids.color_selection_box.canvas: - self.rectangle = RoundedRectangle( - texture=self.texture, - pos=self.ids.gradient_widget.pos, - size=self.ids.gradient_widget.size, - radius=self.color_picker.radius, - group="gradient", - ) - self.bind( - size=lambda instance, size: Clock.schedule_once( - lambda dt: self._update_canvas(instance, size) - ) - ) - - def get_rgba_color_from_touch_region(self, widget, touch) -> List[int]: - """ - Returns the color of the pixel in the gradient that was clicked. - """ - - pixel = widget.texture.get_region(*touch.pos, 1, 1) - rgba = struct.unpack("4B", pixel.pixels) - return rgba - - def updated_canvas(self, widget, touch, color=None) -> None: - """ - Called when clicking on the gradient bar to the right. - Updates the color of the gradient texture. - """ - - if self.color_picker.default_color: - self.color_picker.default_color = None - - self.ids.color_selection_box.canvas.remove_group("gradient") - if not color: - # (0-255, 0-255, 0-255, 0-255) - color = self.get_rgba_color_from_touch_region(widget, touch) - self.create_gradient_texture(color[:-1]) - self.color_picker.dispatch( - "on_select_color", [x / 255.0 for x in color] - ) - else: - self.create_gradient_texture(color) - self.create_canvas_with_gradient_texture(0) - - def on_touch_down(self, touch): - """Handles the ``self.ids.gradient_widget`` touch event.""" - - if self.ids.gradient_widget.collide_point(*touch.pos): - color = self.get_rgba_color_from_touch_region(self, touch) - self.color_picker.dispatch( - "on_select_color", [x / 255.0 for x in color] - ) - return super().on_touch_down(touch) - - def _update_canvas(self, instance_gradient_widget, size: list) -> None: - self.rectangle.size = self.ids.gradient_widget.size - self.rectangle.pos = self.ids.gradient_widget.pos - - -class TabColorList(MDBoxLayout, MDTabsBase): - """Implements a tab for :class:`~ColorListTab` class.""" - - -class ColorListTab(MDTabs): - """ - The class implements a tab with tabs with a list of colors. - This is the second tab on the bottom navigation panel. - """ - - # :class:`~kivymd.uix.colorpicker.MDColorPicker` class. - color_picker = ObjectProperty() - - def generates_list_colors( - self, - instance_color_list_tab, - instance_tab_color_list: TabColorList, - instance_tabs_label: MDTabsLabel, - tab_label_text: str, - ) -> None: - """ - Generates list of colors. - Called when you click the tab of :class:`~TabColorList` class. - """ - - if not tab_label_text: - tab_label_text = "Red" - if not instance_tab_color_list.rv.data: - for hue in _colors[tab_label_text]: - color = get_color_from_hex(_colors[tab_label_text][hue]) - if tab_label_text == "Light": - text_color = (0, 0, 0, 1) - elif tab_label_text == "Dark": - text_color = (1, 1, 1, 1) - else: - text_color = text_colors[tab_label_text][hue] - instance_tab_color_list.rv.data.append( - { - "viewclass": "ColorListItem", - "color": color, - "hue_code": hue, - "text_color": text_color, - "on_press": lambda x=color: self.on_press_color_item(x), - } - ) - - def on_press_color_item(self, color: list) -> None: - """Called when you click on the color item from the list of colors.""" - - rgb = [int(value * 255) for value in color[:-1]] - self.color_picker._rgb = rgb - self.background_color = color - self.color_picker.dispatch("on_select_color", color) - - -class ColorListItem(RectangularRippleBehavior, ButtonBehavior, MDBoxLayout): - """Implements the item for the list of :class:`~TabColorList` class.""" - - color = ColorProperty() - text_color = ColorProperty() - hue_code = StringProperty() - - -class MDColorPicker(BaseDialog): - adjacent_color_constants = ListProperty([0.299, 0.887, 0.411]) - """ - A list of values that are used to create the gradient. These values are - selected empirically. Each of these values will be added to the selected - ``RGB`` value, thus creating colors that are close in value. - - :attr:`adjacent_color_constants` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0.299, 0.887, 0.411]`. - """ - - default_color = ColorProperty(None, allownone=True) - """ - Default color value in (r, g, b, a) or string format. The set color value - will be used when you open the dialog. - - :attr:`default_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - type_color = OptionProperty("RGB", options=["RGBA", "HEX", "RGB"]) - """ - Type of color. - Available options are: `'RGBA'`, `'HEX'`, `'RGB'`. - - :attr:`type_color` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'RGB'`. - """ - - background_down_button_selected_type_color = ColorProperty([1, 1, 1, 0.3]) - """ - Button background for choosing a color type ('RGBA', 'HEX', 'HSL', 'RGB') - in (r, g, b, a) or string format. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/color-picker-background-down-button-selected-type-color.png - :align: center - - :attr:`background_down_button_selected_type_color` is an - :class:`~kivy.properties.ColorProperty` and defaults to `[1, 1, 1, 0.3]`. - """ - - radius_color_scale = VariableListProperty([8]) - """ - The radius value for the color scale. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/color-picker-gradient-scale-radius.png - :align: center - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[8, 8, 8, 8]`. - """ - - text_button_ok = StringProperty("SELECT") - """ - Color selection button text. - - :attr:`text_button_ok` is an :class:`~kivy.properties.StringProperty` - and defaults to `'SELECT'`. - """ - - text_button_cancel = StringProperty("CANCEL") - """ - Cancel button text. - - :attr:`text_button_cancel` is an :class:`~kivy.properties.StringProperty` - and defaults to `'CANCEL'`. - """ - - selected_color = None - # One of the objects of classes: - # :class:`~GradientTab`, :class:`~ColorListTab`, :class:`~SliderTab`. - _current_tab = ObjectProperty() - # The `RGB` value for the transparency preview widget of the selected - # color. - _rgb = ListProperty() - # The opacity value for the transparency preview widget of the selected - # color. - _opacity_value_selected_color = NumericProperty(1) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.gradient_tab = None - self.register_event_type("on_select_color") - self.register_event_type("on_switch_tabs") - self.register_event_type("on_release") - self.on_background_down_button_selected_type_color( - None, self.background_down_button_selected_type_color - ) - - self.on_background_down_button_selected_type_color( - None, self.background_down_button_selected_type_color - ) - Clock.schedule_once(lambda x: self.on_type_color(self), 1) - - def update_color_slider_item_bottom_navigation(self, color: list) -> None: - """ - Updates the color of the slider that sets the transparency value of the - selected color and the color of bottom navigation items. - """ - - if "select_alpha_channel_widget" in self._current_tab.ids: - self._current_tab.ids.select_alpha_channel_widget.ids.slider.color = ( - color - ) - self.ids.bottom_navigation.text_color_active = color - - def update_color_type_buttons(self, color: list) -> None: - """ - Updating button colors (display buttons of type of color) to match the - selected color. - """ - - for instance_toggle_button in self.ids.type_color_button_box.children: - if instance_toggle_button.state != "down": - instance_toggle_button.md_bg_color = color - instance_toggle_button.background_normal = color - - def get_rgb(self, color: list) -> list: - """Returns an ``RGB`` list of values from 0 to 255.""" - - return [ - int(value * 255) - for value in (color[:-1] if len(color) == 4 else color) - ] - - def on_background_down_button_selected_type_color( - self, instance_color_picker, color: list - ) -> None: - def set_background_down(interval: Union[float, int]) -> None: - for ( - instance_toggle_button - ) in self.ids.type_color_button_box.children: - instance_toggle_button.background_down = color - if self.type_color == instance_toggle_button.text: - instance_toggle_button.state = "down" - - Clock.schedule_once(set_background_down) - - def on_type_color( - self, - instance_color_picker, - type_color: str = "", - interval: Union[float, int] = 0, - ) -> None: - """Called when buttons are clicked to set the color type.""" - - if not type_color: - type_color = self.type_color - - if self._rgb: - rgb = self._rgb if self._rgb[0] > 1 else self.get_rgb(self._rgb) - opacity = self._opacity_value_selected_color - color = "" - - if type_color == "RGB": - self.selected_color = [value for value in rgb] - color = f"RGB({', '.join([str(value) for value in self.selected_color])})" - elif type_color == "RGBA": - self.selected_color = [x / 255.0 for x in rgb] + [opacity] - color = f"RGBA({', '.join([str(x / 255.0) for x in rgb])}, {opacity})" - elif type_color == "HEX": - self.selected_color = get_hex_from_color( - [x / 255.0 for x in rgb] + [opacity] - ) - color = f"HEX({self.selected_color})" - - self.ids.lbl_color_value.text = color - - def on_open(self) -> None: - """Default open event handler.""" - - if not self.ids.bottom_navigation_gradient.children: - self.gradient_tab = GradientTab(color_picker=self) - self._current_tab = self.gradient_tab - self.ids.bottom_navigation_gradient.add_widget(self.gradient_tab) - - super().on_open() - - def on_select_color(self, color: list) -> None: - """Called when a gradient image is clicked.""" - - if len(color) == 3: - color += [self._opacity_value_selected_color] - - self.ids.header.md_bg_color = color - self._rgb = color[:-1] - self.on_type_color(self, self.type_color) - self.update_color_type_buttons(color) - self.update_color_slider_item_bottom_navigation(color) - - def on_switch_tabs( - self, - bottom_navigation_instance, - bottom_navigation_item_instance, - name_tab, - ) -> None: - """Called when switching tabs of bottom navigation.""" - - if name_tab == "bottom navigation gradient": - self._current_tab = self.gradient_tab - bottom_navigation_item_instance.children[0].updated_canvas( - None, - None, - self._rgb if self._rgb[0] > 1 else self.get_rgb(self._rgb), - ) - instance_slider_tab = ( - bottom_navigation_instance.ids.tab_manager.get_screen( - "tune" - ).children[0] - ) - select_alpha_channel_widget = ( - self.gradient_tab.ids.select_alpha_channel_widget - ) - select_alpha_channel_widget.ids.slider.value = ( - instance_slider_tab.ids.select_alpha_channel_widget.ids.slider.value - ) - select_alpha_channel_widget.ids.slider.color = [ - x / 255.0 for x in self._rgb - ] + [1] - elif name_tab == "tune": - if self._rgb[0] <= 1: - color = self.get_rgb(self._rgb) - else: - color = self._rgb - instance_slider_tab = self.ids.tune.children[0] - self._current_tab = instance_slider_tab - instance_slider_tab.ids.slider_red.ids.slider.value = color[0] - instance_slider_tab.ids.slider_green.ids.slider.value = color[1] - instance_slider_tab.ids.slider_blue.ids.slider.value = color[2] - instance_slider_tab.ids.select_alpha_channel_widget.ids.slider.value = ( - self._opacity_value_selected_color - ) - elif name_tab == "view headline": - color = self._rgb + [1] - color_list_tabs = self.ids.view_headline.children[0] - self._current_tab = color_list_tabs - try: - color_list_tabs.background_color = color - except ValueError: - color_list_tabs.background_color = [x / 255.0 for x in color][ - :-1 - ] + [1] - if not color_list_tabs.get_tab_list(): - for color in _colors.keys(): - tab_widget = TabColorList(title=str(color)) - color_list_tabs.add_widget(tab_widget) - - def on_release(self, *args): - """Called when the `SELECT` button is pressed""" - - def _get_selected_color(self, selected_color: Union[list, str]) -> list: - """ - Convert [0-255, 0-255, 0-255] and '#rrggbb' to kivy color format. - Return kivy color format. - """ - - rgba = [0, 0, 0, 0] - if isinstance(selected_color, list): - if selected_color[0] > 1: - rgba = [x / 255.0 for x in selected_color] + [ - self._opacity_value_selected_color - ] - else: - rgba = selected_color - elif isinstance(selected_color, str): - rgba = get_color_from_hex(selected_color)[:-1] + [ - self._opacity_value_selected_color - ] - return rgba diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__init__.py index 994878c..fa0fd8c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__init__.py @@ -1,5 +1,5 @@ from .datepicker import ( # NOQA F401 - BaseDialogPicker, - DatePickerInputField, - MDDatePicker, + MDDockedDatePicker, + MDModalDatePicker, + MDModalInputDatePicker, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/__init__.cpython-311.pyc index bc837c6..1cf2b3f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/datepicker.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/datepicker.cpython-311.pyc index a403933..9102e36 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/datepicker.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/__pycache__/datepicker.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.kv index 7fe2866..916029c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.kv @@ -1,392 +1,448 @@ -#:import os os -#:import date datetime.date -#:import calendar calendar -#:import platform platform #:import Clock kivy.clock.Clock -#:import images_path kivymd.images_path + +############################################################################### +# +# MODAL INPUT RULES +# +############################################################################### + + + mode: "outlined" + validator: "date" - - on_enter: - self.tooltip_text = "" if self.owner \ - and self.owner._input_date_dialog_open \ - or self.owner._select_year_dialog_open \ - else self.hint_text - - - - - - - _calendar_layout: _calendar_layout + + orientation: "vertical" + padding: 0, dp(16), 0, dp(16) size_hint: None, None - size: - (dp(328), dp(512) - root._shift_dialog_height) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(528), dp(328) - root._shift_dialog_height) + size: dp(328), dp(280) + pos_hint: {"center_x": .5, "center_y": .5} - MDRelativeLayout: - id: container - background: os.path.join(images_path, "transparent.png") + MDLabel: + id: supporting_label + adaptive_size: True + text: root.supporting_text + padding: dp(16), 0, 0, dp(36) + font_style: "Label" + role: "large" - canvas: - Color: - rgb: root.primary_color or app.theme_cls.primary_color - RoundedRectangle: - size: - (dp(328), dp(120)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168), dp(328) - root._shift_dialog_height) - pos: - (0, root.height - dp(120)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (0, 0) - radius: - (root.radius[0], root.radius[1], dp(0), dp(0)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (root.radius[0], dp(0), dp(0), root.radius[3]) - Color: - rgba: root.accent_color or app.theme_cls.bg_normal - RoundedRectangle: - size: - (dp(328), dp(512) - dp(120) - root._shift_dialog_height) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(360), dp(328) - root._shift_dialog_height) - pos: - (0, 0) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168), 0) - radius: - (dp(0), dp(0), root.radius[2], root.radius[3]) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(0), root.radius[1], root.radius[2], dp(0)) + BoxLayout: + size_hint_y: None + height: self.minimum_height + padding: dp(16), 0, dp(8), dp(16) MDLabel: - id: label_title - font_style: "Body2" - bold: True - theme_text_color: "Custom" - size_hint_x: None - width: root.width - adaptive_height: True - text: root.title - font_name: root.font_name - pos: - (dp(24), root.height - self.height - dp(18)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(24), root.height - self.height - dp(24)) - text_color: root.text_toolbar_color or root.specific_text_color + id: current_month_name + adaptive_size: True + text: root._current_month_name + text_color: self.theme_cls.onSurfaceVariantColor + font_style: "Headline" + role: "large" + theme_line_height: "Custom" + line_height: 1 + theme_font_size: "Custom" + font_size: "32sp" if root.mode == "picker" else "20sp" + pos_hint: {"center_y": .5} - MDLabel: - id: label_full_date - font_style: "H4" - theme_text_color: "Custom" - size_hint_x: None - width: root.width - adaptive_height: True - font_name: root.font_name - markup: True - pos: - (dp(24), root.height - dp(120) + dp(18)) \ - if root.theme_cls.device_orientation == "portrait" \ - else \ - ( \ - dp(24) if not root._input_date_dialog_open else dp(168) + dp(24), \ - root.height - self.height - dp(96) \ - ) - text: root._date_label_text - text_color: - root.text_toolbar_color or root.specific_text_color \ - if root.theme_cls.device_orientation == "portrait" else \ - root.primary_color or self.theme_cls.primary_color \ - if root._input_date_dialog_open else \ - root.text_toolbar_color or root.specific_text_color - - RecycleView: - id: _year_layout - key_viewclass: "viewclass" - size_hint: None, None - size: _calendar_layout.size - pos: _calendar_layout.pos - disabled: True - - canvas.before: - PushMatrix - Scale: - x: root._scale_year_layout - y: root._scale_year_layout - origin: self.center - canvas.after: - PopMatrix - - SelectYearList: - cols: 3 - default_size: dp(170), dp(36) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height + Widget: MDIconButton: - id: edit_icon - icon: "pencil" - icon_size: "24sp" - theme_icon_color: "Custom" - on_release: - root.transformation_to_dialog_input_date() \ - if not root._input_date_dialog_open else \ - Clock.schedule_once(root.transformation_from_dialog_input_date, .15) - x: - (root.width - self.width - dp(12)) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(12) - y: - (root.height - dp(120) + dp(12)) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(12) - text_color: root.text_toolbar_color or root.specific_text_color + id: icon_button + icon: "calendar" + on_release: root.dispatch("on_edit") + + MDDivider: + + MDModalInputDatePickerInputDateFieldContainer + id: input_date_container + padding: dp(16), 0, dp(16), dp(24) + + MDModalInputDatePickerInputField: + id: input_date_field + date_picker: root + date_format: root.date_format + + MDTextFieldHintText: + text: root.date_format + + MDTextFieldHelperText: + text: root.error_text + mode: "on_error" + + MDDatePickerButtonsContainer: + date_picker: root + +############################################################################### +# +# MODAL RULES +# +############################################################################### + + + size_hint_x: None + valign: "middle" + halign: "center" + text_color: + self.theme_cls.onSurfaceColor \ + if not root.selected else \ + self.theme_cls.onPrimaryColor + + canvas.before: + Color: + rgba: + self.theme_cls.primaryColor \ + if self.selected else \ + self.theme_cls.transparentColor + RoundedRectangle: + pos: self.x + dp(12), self.y + dp(6) + size: self.width - dp(24), self.height - dp(8) + radius: [(root.height / 2) - dp(4), ] + + + + scale_value_x: 1.5 + scale_value_y: 1.5 + key_viewclass: "viewclass" + key_size: "height" + bar_width: 0 + + MDModalDatePickerContainerMenuYearSelection: + cols: 3 + padding: dp(32), 0, 0, 0 + default_size: None, dp(48) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + multiselect: False + touch_multiselect: True + + + + canvas: + Color: + rgba: self.color[:-1] + [self.alpha] + Rectangle: + pos: self.pos + size: self.size + + + + size_hint_y: None + height: dp(48) + + MDLabel: + id: label + adaptive_size: True + text: root.text + pos_hint: {"center_y": .5} + padding: 0, 0, "8dp", 0 + font_style: "Label" + role: "large" + + MDDatePickerBaseMenuSelectionButton + id: menu_selection_button + pos_hint: {"center_y": .5} + icon: "menu-right" + date_picker: root.date_picker + on_release: root.dispatch("on_open_menu") + + Widget: + + MDIconButton: + id: chevron_left + icon: "chevron-left" + pos_hint: {"center_y": .5} + on_release: root.dispatch("on_release", "prev") + + MDIconButton: + id: chevron_right + icon: "chevron-right" + pos_hint: {"center_y": .5} + on_release: root.dispatch("on_release", "next") + + + + calendar_layout: calendar_layout + orientation: "vertical" + padding: 0, 0, 0, "12dp" + pos_hint: {'center_x': .5, 'center_y': .5} + size_hint: None, None + size: calendar_layout.width - self.padding[0] / 2, dp(520) + + MDLabel: + id: supporting_label + adaptive_size: True + text: root.supporting_text + padding: dp(24), 0, 0, dp(36) + font_style: "Label" + role: "large" + + BoxLayout: + size_hint_y: None + height: self.minimum_height + padding: dp(24), 0, dp(12), dp(16) MDLabel: - id: label_month_selector - font_style: "Body2" - -text_size: None, None - theme_text_color: "Custom" + id: current_month_name adaptive_size: True - text: calendar.month_name[root.month].capitalize() + " " + str(root.year) - font_name: root.font_name - pos: - (dp(24), root.height - dp(120) - self.height - dp(20)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168) + dp(24), label_title.y) - text_color: root.text_color or app.theme_cls.text_color + text: root._current_month_name + text_color: self.theme_cls.onSurfaceVariantColor + font_style: "Headline" + role: "large" - DatePickerIconTooltipButton: - id: triangle - owner: root - icon: "menu-down" - ripple_scale: .5 - theme_icon_color: "Custom" - hint_text: "Choose year" - on_release: - root.transformation_to_dialog_select_year() \ - if not root._select_year_dialog_open else \ - root.transformation_from_dialog_select_year() - pos: - (label_month_selector.width + dp(14), root.height - dp(123) - self.height) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(180) + label_month_selector.width, label_title.y - dp(14)) - text_color: root.text_color or app.theme_cls.text_color - md_bg_color_disabled: 0, 0, 0, 0 + Widget: - DatePickerIconTooltipButton: - id: chevron_left - owner: root - icon: "chevron-left" - on_release: root.change_month("prev") - theme_icon_color: "Custom" - hint_text: "Previous month" - x: - dp(228) if root.theme_cls.device_orientation == "portrait" \ - else dp(418) - y: - root.height - dp(120) - self.height / 2 - dp(30) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(272) - text_color: root.text_color or app.theme_cls.text_color + MDIconButton: + id: icon_button + icon: "pencil" + on_release: root.dispatch("on_edit") - DatePickerIconTooltipButton: - id: chevron_right - owner: root - icon: "chevron-right" - on_release: root.change_month("next") - theme_icon_color: "Custom" - hint_text: "Next month" - x: - dp(272) if root.theme_cls.device_orientation == "portrait" \ - else dp(464) - y: - root.height - dp(120) - self.height / 2 - dp(30) \ - if root.theme_cls.device_orientation == "portrait" \ - else dp(272) - text_color: root.text_color or app.theme_cls.text_color + MDDivider: - # TODO: Replace the GridLayout with a RecycleView - # if it improves performance. - GridLayout: - id: _calendar_layout - cols: 7 + MDModalDatePickerYearSelectionItem: + id: year_selection_items + text: root._current_full_month_name + date_picker: root + padding: dp(24), 0, dp(12), 0 + + RelativeLayout: + size_hint: None, None + size: calendar_layout.size + + MDCalendarLayout: + id: calendar_layout + padding: dp(12), 0, dp(12), 0 + + MDModalDatePickerMenuYearSelection + id: year_selection_layout size_hint: None, None - size: - (dp(44 * 7), dp(40 * 7)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(46 * 7), dp(32 * 7)) - col_default_width: - dp(42) if root.theme_cls.device_orientation == "portrait" \ - else dp(39) - padding: - (dp(2), 0) if root.theme_cls.device_orientation == "portrait" \ - else (dp(7), 0) - spacing: - (dp(2), 0) if root.theme_cls.device_orientation == "portrait" \ - else (dp(7), 0) - pos: - (dp(10), dp(56)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(168) + dp(20), dp(44)) + size: 0, 0 - canvas.before: - PushMatrix - Scale: - x: root._scale_calendar_layout - y: root._scale_calendar_layout - origin: self.center - canvas.after: - PopMatrix + MDDatePickerButtonsContainer: + date_picker: root - MDFlatButton: - id: ok_button - width: dp(32) - pos: root.width - self.width, dp(10) - text: "OK" - theme_text_color: "Custom" - font_name: root.font_name - text_color: root.text_button_color or root.theme_cls.primary_color - on_release: root.on_ok_button_pressed() +############################################################################### +# +# DOCKED RULES +# +############################################################################### - MDFlatButton: - id: cancel_button - text: "CANCEL" - on_release: root.dispatch("on_cancel", None) - theme_text_color: "Custom" - pos: root.width - self.width - ok_button.width - dp(10), dp(10) - font_name: root.font_name - text_color: root.text_button_color or root.theme_cls.primary_color - - - + + calendar_layout: calendar_layout size_hint: None, None - size: - (dp(42), dp(42)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) - disabled: True + size: calendar_layout.width - self.padding[0] / 2, dp(406) + orientation: "vertical" + padding: "12dp", 0, "12dp", "12dp" + + BoxLayout: + id: month_year_selection_items_container + size_hint_y: None + height: self.minimum_height + padding: 0, 0, 0, "12dp" + + MDDockedDatePickerMonthSelectionItem: + id: month_selection_items + text: root._current_month_name + date_picker: root + + Widget: + + MDDockedDatePickerYearSelectionItem: + id: year_selection_items + text: str(root.year) + date_picker: root + + RelativeLayout: + size_hint: None, None + size: calendar_layout.size + + MDCalendarLayout: + id: calendar_layout + + MDDockedDatePickerMenuMonthYearSelection + id: month_year_selection_layout + size_hint: None, None + size: 0, 0 + + MDDatePickerButtonsContainer: + id: button_container + date_picker: root + + + + scale_value_x: 0 + scale_value_y: 0 + key_viewclass: "viewclass" + key_size: "height" + bar_width: 0 + + MDDockedDatePickerContainerMenuMonthYearSelection: + padding: dp(8), 0, 0, 0 + default_size: None, dp(48) + default_size_hint: 1, None + size_hint_y: None + height: self.minimum_height + orientation: "vertical" + multiselect: False + touch_multiselect: True + + + + spacing: "8dp" + size_hint_y: None + + MDIcon: + icon: "check" + pos_hint: {"center_y": .5} + icon_color: + self.theme_cls.transparentColor \ + if not root.selected else \ + self.theme_cls.onSurfaceColor + + MDLabel: + text: root.month_year_name + + + + size_hint: None, None + size: self.minimum_size + + MDIconButton: + id: chevron_left + icon: "chevron-left" + on_release: root.dispatch("on_release", "prev") + + MDLabel: + id: label + adaptive_size: True + text: root.text + pos_hint: {"center_y": .5} + padding: 0, 0, "8dp", 0 + font_style: "Label" + role: "large" + + MDDatePickerBaseMenuSelectionButton + id: menu_selection_button + pos_hint: {"center_y": .5} + icon: "menu-right" + date_picker: root.date_picker + on_release: root.dispatch("on_open_menu") + + MDIconButton: + id: chevron_right + icon: "chevron-right" + on_release: root.dispatch("on_release", "next") + +############################################################################### +# +# COMMON RULES +# +############################################################################### + + + elevation_level: 0 + shadow_color: self.theme_cls.transparentColor + + canvas.before: + Color: + rgba: + self.theme_cls.surfaceContainerHighColor + RoundedRectangle: + size: self.size + pos: self.pos + radius: self.radius + + + + cols: 7 + size_hint: None, None + width: dp(46 * 7) + height: self.minimum_height + + + + size_hint_y: None + height: self.minimum_height + padding: 0, 0, dp(12), 0 + + Widget: + + MDButton: + style: "text" + on_release: root.date_picker.dispatch("on_cancel") + + MDButtonText: + text: + root.date_picker.text_button_cancel \ + if root.date_picker else \ + "" + + MDButton: + style: "text" + on_release: root.date_picker.dispatch("on_ok") + + MDButtonText: + text: + root.date_picker.text_button_ok \ + if root.date_picker else \ + "" + + + + icon: "menu-right" + + + + size_hint: None, None + text_size: self.size + halign: "center" + valign: "middle" + size: dp(40), dp(40) + + + + size_hint: None, None + size: dp(42), dp(42) + halign: "center" + radius: self.height / 2 + line_color: + self.theme_cls.primaryColor \ + if self.is_today and self.date_picker.mark_today else \ + self.theme_cls.transparentColor + text_color: + self.theme_cls.onSurfaceColor \ + if not root.is_selected else \ + self.theme_cls.onPrimaryColor # Fill marking the available dates of the range, if using the `range` mode # or use `min_date/max_date`. canvas.before: Color: rgba: - (self.owner.selector_color or self.theme_cls.primary_color)[:-1] + [.3] \ + self.theme_cls.primaryColor[:-1] + [.3] \ if self.is_in_range \ - else (0, 0, 0, 0) + else \ + self.theme_cls.transparentColor RoundedRectangle: size: - (dp(44), dp(32)) \ - if root.theme_cls.device_orientation == "portrait" \ - else \ - (dp(32), dp(28)) \ + (dp(42), dp(32)) \ if self.is_range_end or self.is_week_end or self.is_month_end \ - else (dp(46), dp(28)) - pos: - (self.x - dp(1.5), self.y + dp(5)) \ - if root.theme_cls.device_orientation == "portrait" else \ - (self.x, self.y + 1) + else (dp(42), dp(32)) + pos: self.x, self.y + dp(6) radius: [ - self.width / 2 if self.is_range_start else 0, - self.width / 2 if self.is_range_end else 0, - self.width / 2 if self.is_range_end else 0, - self.width / 2 if self.is_range_start else 0, + dp(32) / 2 if self.is_range_start else 0, + dp(32) / 2 if self.is_range_end else 0, + dp(32) / 2 if self.is_range_end else 0, + dp(32) / 2 if self.is_range_start else 0, ] # Selection circle. Color: rgba: - root.owner.selector_color or self.theme_cls.primary_color \ + self.theme_cls.primaryColor \ if root.is_selected and not self.disabled \ - else (0, 0, 0, 0) + else self.theme_cls.transparentColor Ellipse: - size: - (dp(42), dp(42)) \ - if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) + size: dp(42), dp(42) pos: self.pos - - MDLabel: - font_style: "Caption" - size_hint_x: None - halign: "center" - text: root.text - font_name: root.owner.font_name - theme_text_color: "Custom" - text_color: - root.owner.accent_color or root.theme_cls.bg_normal \ - if root.is_selected else \ - root.owner.text_current_color or root.theme_cls.primary_color \ - if root.is_today else \ - root.owner.text_color or root.theme_cls.text_color - - - font_style: "Caption" - theme_text_color: "Custom" - size_hint: None, None - text_size: self.size - halign: "center" - valign: - "middle" if root.theme_cls.device_orientation == "portrait" \ - else "center" - size: - (dp(40), dp(40)) if root.theme_cls.device_orientation == "portrait" \ - else (dp(32), dp(32)) - text_color: root.owner.text_weekday_color or app.theme_cls.disabled_hint_text_color - - - - font_style: "Caption" - size_hint_x: None - valign: "middle" - halign: "center" - text: root.text - theme_text_color: "Custom" - text_color: - (0, 0, 0, 0) \ - if self.owner is None else \ - self.owner.accent_color or self.owner.theme_cls.bg_normal \ - if self.selected else \ - self.owner.text_color or self.owner.theme_cls.text_color - on_text: root.font_name = root.owner.font_name - - canvas.before: - Color: - rgba: - self.owner.selector_color or self.theme_cls.primary_color \ - if self.selected else \ - (0, 0, 0, 0) - RoundedRectangle: - pos: self.x + dp(12), self.y - size: self.width - dp(24), self.height - radius: [root.height / 2, ] - - - - adaptive_height: True - size_hint_x: None - spacing: dp(8) - opacity: 0 - width: - self.owner.width - dp(48) \ - if root.owner.theme_cls.device_orientation == "portrait" \ - else self.owner.width - dp(168) - dp(48) - y: - self.owner.height - dp(123) - self.height - dp(20) \ - if root.owner.theme_cls.device_orientation == "portrait" \ - else self.owner.height - self.height - dp(24) - x: - dp(24) if root.owner.theme_cls.device_orientation == "portrait" \ - else dp(168) + dp(24) - - - - mode: "fill" - hint_text: "dd/mm/yyyy" - input_filter: root.input_filter - fill_color: root.owner.input_field_background_color or (0, 0, 0, .15) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.py index 3b24f9e..cb9910e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/datepicker/datepicker.py @@ -4,237 +4,564 @@ Components/DatePicker .. seealso:: - `Material Design spec, Date picker `_ + `Material Design spec, Date picker `_ -.. rubric:: Includes date picker. +.. rubric:: Date pickers let people select a date, or a range of dates. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/date-picker.png :align: center -.. rubric:: Usage +- Date pickers can display past, present, or future dates +- Three types: docked, modal, modal input +- Clearly indicate important dates, such as current and selected days +- Follow common patterns, like a calendar view -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.pickers import MDDatePicker - - KV = ''' - MDFloatLayout: - - MDRaisedButton: - text: "Open date picker" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_date_picker() - ''' - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_save(self, instance, value, date_range): - ''' - Events called when the "OK" dialog box button is clicked. - - :type instance: ; - :param value: selected date; - :type value: ; - :param date_range: list of 'datetime.date' objects in the selected range; - :type date_range: ; - ''' - - print(instance, value, date_range) - - def on_cancel(self, instance, value): - '''Events called when the "CANCEL" dialog box button is clicked.''' - - def show_date_picker(self): - date_dialog = MDDatePicker() - date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) - date_dialog.open() - - - Test().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton - from kivymd.uix.pickers import MDDatePicker - from kivymd.uix.screen import MDScreen - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDRaisedButton( - text="Open data picker", - pos_hint={'center_x': .5, 'center_y': .5}, - on_release=self.show_date_picker, - ) - ) - ) - - def on_save(self, instance, value, date_range): - ''' - Events called when the "OK" dialog box button is clicked. - - :type instance: ; - - :param value: selected date; - :type value: ; - - :param date_range: list of 'datetime.date' objects in the selected range; - :type date_range: ; - ''' - - print(instance, value, date_range) - - def on_cancel(self, instance, value): - '''Events called when the "CANCEL" dialog box button is clicked.''' - - def show_date_picker(self, *args): - date_dialog = MDDatePicker() - date_dialog.bind(on_save=self.on_save, on_cancel=self.on_cancel) - date_dialog.open() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDDatePicker.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/date-picker-types.png :align: center -Open date dialog with the specified date ----------------------------------------- +1. Docked date picker +2. Modal date picker +3. Modal date input + +KivyMD provides the following date pickers classes for use: + +- MDDockedDatePicker_ +- MDModalDatePicker_ +- MDModalInputDatePicker_ + +.. _MDDockedDatePicker: + +MDDockedDatePicker +------------------ + +Docked datepickers allow the selection of a specific date and year. The docked +datepicker displays a date input field by default, and a dropdown calendar +appears when the user taps on the input field. Either form of date entry can +be interacted with. + +Docked date pickers are ideal for navigating dates in both the near future or +past and the distant future or past, as they provide multiple ways to select +dates. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/docked-data-picker-preview.gif + :align: center .. code-block:: python - def show_date_picker(self): - date_dialog = MDDatePicker(year=1983, month=4, day=12) - date_dialog.open() + from kivy.lang import Builder + from kivy.metrics import dp -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/specified-date.png + from kivymd.app import MDApp + from kivymd.uix.pickers import MDDockedDatePicker + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTextField: + id: field + mode: "outlined" + pos_hint: {'center_x': .5, 'center_y': .85} + size_hint_x: .5 + on_focus: app.show_date_picker(self.focus) + + MDTextFieldHintText: + text: "Docked date picker" + + MDTextFieldHelperText: + text: "MM/DD/YYYY" + mode: "persistent" + + MDTextFieldTrailingIcon: + icon: "calendar" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + def show_date_picker(self, focus): + if not focus: + return + + date_dialog = MDDockedDatePicker() + # You have to control the position of the date picker dialog yourself. + date_dialog.pos = [ + self.root.ids.field.center_x - date_dialog.width / 2, + self.root.ids.field.y - (date_dialog.height + dp(32)), + ] + date_dialog.open() + + + Example().run() + +.. _MDModalDatePicker: + +MDModalDatePicker +----------------- + +Modal date pickers navigate across dates in several ways: + +- To navigate across months, swipe horizontally (not implemented in KivyMD) +- To navigate across years, scroll vertically (not implemented in KivyMD) +- To access the year picker, tap the year + +Don’t use a modal date picker to prompt for dates in the distant past or +future, such as a date of birth. In these cases, use a modal input picker or +a docked datepicker instead. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-preview.gif :align: center -Interval date -------------- - -You can set the time interval from and to the set date. All days of the week -that are not included in this range will have the status `disabled`. - .. code-block:: python - def show_date_picker(self): - date_dialog = MDDatePicker( + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDModalDatePicker + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_date_picker() + + MDButtonText: + text: "Open modal date picker dialog" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + def show_date_picker(self): + date_dialog = MDModalDatePicker() + date_dialog.open() + + + Example().run() + +.. _MDModalInputDatePicker: + +MDModalInputDatePicker +---------------------- + +Modal date inputs allow the manual entry of dates using the numbers on a +keyboard. Users can input a date or a range of dates in a dialog. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-input-data-picker-preview.gif + :align: center + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDModalInputDatePicker + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_date_picker() + + MDButtonText: + text: "Open modal date picker dialog" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + def show_date_picker(self): + date_dialog = MDModalInputDatePicker() + date_dialog.open() + + + Example().run() + +The range of available dates +============================ + +To display only the selected date range, use the `min_date` and `max_date` +parameters: + +.. code-block:: python + + def show_modal_date_picker(self, *args): + MDModalDatePicker( + mark_today=False, min_date=datetime.date.today(), max_date=datetime.date( datetime.date.today().year, datetime.date.today().month, - datetime.date.today().day + 2, + datetime.date.today().day + 4, ), - ) - date_dialog.open() + ).open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/range-date.png +Only dates in the specified range will be available for selection: + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-min-max-date.gif :align: center -The range of available dates can be changed in the picker dialog: +Select the date range +===================== -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/change-range-date.gif - :align: center - -Select year ------------ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/select-year-date.gif - :align: center - -.. warning:: The list of years when opening is not automatically set - to the current year. - -You can set the range of years using the :attr:`~kivymd.uix.picker.MDDatePicker.min_year` and -:attr:`~kivymd.uix.picker.MDDatePicker.max_year` attributes: +To select the date range, use the `mode` parameter with the value "range": .. code-block:: python - def show_date_picker(self): - date_dialog = MDDatePicker(min_year=2022, max_year=2030) - date_dialog.open() + def show_modal_date_picker(self, *args): + MDModalDatePicker(mode="range").open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/min-max-year-date.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-range.gif :align: center -Set and select a date range ---------------------------- +Setting the date range manually +=============================== .. code-block:: python - def show_date_picker(self): - date_dialog = MDDatePicker(mode="range") - date_dialog.open() + def show_modal_date_picker(self, *args): + MDModalInputDatePicker(mode="range").open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/set-select-range-date.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-input-data-picker-range.gif :align: center + +Events +====== + +**on_edit** event +----------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-edit.gif + :align: center + +.. code-block:: python + + from kivy.clock import Clock + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDModalInputDatePicker, MDModalDatePicker + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_modal_date_picker() + + MDButtonText: + text: "Open modal date picker dialog" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + def show_modal_input_date_picker(self, *args): + def on_edit(*args): + date_dialog.dismiss() + Clock.schedule_once(self.show_modal_date_picker, 0.2) + + date_dialog = MDModalInputDatePicker() + date_dialog.bind(on_edit=on_edit) + date_dialog.open() + + def on_edit(self, instance_date_picker): + instance_date_picker.dismiss() + Clock.schedule_once(self.show_modal_input_date_picker, 0.2) + + def show_modal_date_picker(self, *args): + date_dialog = MDModalDatePicker() + date_dialog.bind(on_edit=self.on_edit) + date_dialog.open() + + + Example().run() + +**on_select_day** event +----------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-select-day.gif + :align: center + +.. code-block:: python + + from kivy.lang import Builder + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDModalDatePicker + from kivymd.uix.snackbar import MDSnackbar, MDSnackbarSupportingText + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_modal_date_picker() + + MDButtonText: + text: "Open modal date picker dialog" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + def on_select_day(self, instance_date_picker, number_day): + instance_date_picker.dismiss() + MDSnackbar( + MDSnackbarSupportingText( + text=f"The selected day is {number_day}", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + background_color="olive" + ).open() + + def show_modal_date_picker(self, *args): + date_dialog = MDModalDatePicker() + date_dialog.bind(on_select_day=self.on_select_day) + date_dialog.open() + + + Example().run() + +**on_select_month** event +------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-select-month.gif + :align: center + +.. code-block:: python + + def on_select_month(self, instance_date_picker, number_month): + [...] + + def show_modal_date_picker(self, *args): + [...] + date_dialog.bind(on_select_month=self.on_select_month) + [...] + +**on_select_year** event +------------------------ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-select-year.gif + :align: center + +.. code-block:: python + + def on_select_year(self, instance_date_picker, number_year): + [...] + + def show_modal_date_picker(self, *args): + [...] + date_dialog.bind(on_select_month=self.on_select_year) + [...] + +**on_cancel** event +------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-cancel.gif + :align: center + +.. code-block:: python + + def on_cancel(self, instance_date_picker): + [...] + + def show_modal_date_picker(self, *args): + [...] + date_dialog.bind(on_cancel=self.on_cancel) + [...] + +**on_ok** event +--------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-ok.gif + :align: center + +.. code-block:: python + + def on_ok(self, instance_date_picker): + print(instance_date_picker.get_date()[0]) + + def show_modal_date_picker(self, *args): + [...] + date_dialog.bind(on_ok=self.on_ok) + [...] + +**on_ok** with range event +-------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/modal-data-picker-event-on-ok-with-range.gif + :align: center + +.. code-block:: python + + import datetime + + from kivy.lang import Builder + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDModalDatePicker + from kivymd.uix.snackbar import ( + MDSnackbar, MDSnackbarSupportingText, MDSnackbarText + ) + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_modal_date_picker() + + MDButtonText: + text: "Open modal date picker dialog" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + def on_ok(self, instance_date_picker): + MDSnackbar( + MDSnackbarText( + text="Selected dates is:", + ), + MDSnackbarSupportingText( + text="\\n".join(str(date) for date in instance_date_picker.get_date()), + padding=[0, 0, 0, dp(12)], + ), + y=dp(124), + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + padding=[0, 0, "8dp", "8dp"], + ).open() + + def show_modal_date_picker(self, *args): + date_dialog = MDModalDatePicker( + mode="range", + min_date=datetime.date.today(), + max_date=datetime.date( + datetime.date.today().year, + datetime.date.today().month, + datetime.date.today().day + 4, + ), + ) + date_dialog.bind(on_ok=self.on_ok) + date_dialog.open() + + + Example().run() + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + date_dialog = MDDatePicker() + date_dialog.open() + +2.0.0 version +------------- + +.. code-block:: python + + # date_dialog = MDModalDatePicker() + # date_dialog = MDModalInputDatePicker() + + date_dialog = MDDockedDatePicker() + date_dialog.open() """ +# TODO: Should we implement a full-screen date picker dialog? +# Implement a tooltip for time picker elements. +# Should we implement the feature to settings custom colors for the +# date picker dialogs? + from __future__ import annotations -__all__ = ("MDDatePicker", "BaseDialogPicker", "DatePickerInputField") +__all__ = ( + "MDBaseDatePicker", + "MDDockedDatePicker", + "MDModalDatePicker", + "MDModalInputDatePicker", +) +import os import calendar import datetime -import math -import os -import time from datetime import date from itertools import zip_longest -from typing import Union from kivy.animation import Animation +from kivy.clock import Clock +from kivy.core.window import Window from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, NumericProperty, - ObjectProperty, OptionProperty, + ObjectProperty, + BooleanProperty, StringProperty, + ColorProperty, + VariableListProperty, ) -from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.behaviors import ButtonBehavior, FocusBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.gridlayout import GridLayout +from kivy.uix.recycleboxlayout import RecycleBoxLayout from kivy.uix.recyclegridlayout import RecycleGridLayout +from kivy.uix.recycleview import RecycleView from kivy.uix.recycleview.layout import LayoutSelectionBehavior from kivy.uix.recycleview.views import RecycleDataViewBehavior +from kivy.uix.widget import Widget from kivymd import uix_path -from kivymd.theming import ThemableBehavior, ThemeManager -from kivymd.toast import toast +from kivymd.theming import ThemableBehavior from kivymd.uix.behaviors import ( + ScaleBehavior, CircularRippleBehavior, + RotateBehavior, CommonElevationBehavior, - SpecificBackgroundColorBehavior, ) -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDIconButton -from kivymd.uix.dialog import BaseDialog -from kivymd.uix.label import MDLabel -from kivymd.uix.textfield import MDTextField -from kivymd.uix.tooltip import MDTooltip +from kivymd.uix.behaviors.motion_behavior import MotionDatePickerBehavior +from kivymd.uix.label import MDLabel, MDIcon +from kivymd.uix.textfield import ( + MDTextField, + MDTextFieldHintText, + MDTextFieldHelperText, +) with open( os.path.join(uix_path, "pickers", "datepicker", "datepicker.kv"), @@ -243,550 +570,37 @@ with open( Builder.load_string(kv_file.read()) -class BaseDialogPicker( - BaseDialog, - CommonElevationBehavior, - SpecificBackgroundColorBehavior, -): +class MDDatePickerTypeDateError(Exception): + pass + + +class MDBaseDatePicker(ThemableBehavior, MotionDatePickerBehavior, BoxLayout): """ - Base class for :class:`~kivymd.uix.picker.MDDatePicker` and - :class:`~kivymd.uix.picker.MDTimePicker` classes. + Implements the base class of the date picker. + + .. versionadded:: 2.0.0 For more information, see in the - :class:`~kivymd.uix.dialog.BaseDialog` and - :class:`~kivymd.uix.behaviors.CommonElevationBehavior` and - :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior` + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.motion_behavior.MotionDatePickerBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` and classes documentation. :Events: - `on_save` - Events called when the "OK" dialog box button is clicked. - `on_cancel` - Events called when the "CANCEL" dialog box button is clicked. - """ - - title_input = StringProperty("INPUT DATE") - """ - Dialog title fot input date. - - .. code-block:: python - - MDDatePicker(title_input="INPUT DATE") - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-input-date.png - :align: center - - :attr:`title_input` is an :class:`~kivy.properties.StringProperty` - and defaults to `INPUT DATE`. - """ - - title = StringProperty("SELECT DATE") - """ - Dialog title fot select date. - - .. code-block:: python - - MDDatePicker(title="SELECT DATE") - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-select-date.png - :align: center - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `SELECT DATE`. - """ - - radius = ListProperty([7, 7, 7, 7]) - """ - Radius list for the four corners of the dialog. - - .. code-block:: python - - MDDatePicker(radius=[7, 7, 7, 26]) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-radius.png - :align: center - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[7, 7, 7, 7]`. - """ - - primary_color = ColorProperty(None) - """ - Background color of toolbar in (r, g, b, a) or string format. - - .. code-block:: python - - MDDatePicker(primary_color="brown") - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/primary-color-date.png - :align: center - - :attr:`primary_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - accent_color = ColorProperty(None) - """ - Background color of calendar/clock face in (r, g, b, a) or string format. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/accent-color-date.png - :align: center - - :attr:`accent_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - selector_color = ColorProperty(None) - """ - Background color of the selected day of the month or hour in (r, g, b, a) - or string format. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selector-color-date.png - :align: center - - :attr:`selector_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_toolbar_color = ColorProperty(None) - """ - Color of labels for text on a toolbar in (r, g, b, a) or string format. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-toolbar-color-date.png - :align: center - - :attr:`text_toolbar_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_color = ColorProperty(None) - """ - Color of text labels in calendar/clock face in (r, g, b, a) or string format. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-color-date.png - :align: center - - :attr:`text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_current_color = ColorProperty(None) - """ - Color of the text of the current day of the month/hour in (r, g, b, a) - or string format. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-current-color-date.png - :align: center - - :attr:`text_current_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_button_color = ColorProperty(None) - """ - Text button color in (r, g, b, a) format. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - text_button_color="lightgrey", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-button-color-date.png - :align: center - - :attr:`text_button_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - input_field_background_color_normal = ColorProperty(None) - """ - Background color normal of input fields in (r, g, b, a) or string format. - - .. versionadded:: 1.1.0 - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - text_button_color="lightgrey", - input_field_background_color_normal="coral", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-date.png - :align: center - - :attr:`input_field_background_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - input_field_background_color_focus = ColorProperty(None) - """ - Background color normal of input fields in (r, g, b, a) or string format. - - .. versionadded:: 1.1.0 - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - text_button_color="lightgrey", - input_field_background_color_normal="coral", - input_field_background_color_focus="red", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-background-color-focus-date.png - :align: center - - :attr:`input_field_background_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - input_field_background_color = ColorProperty(None) - """ - .. deprecated:: 1.1.0 - Use :attr:`input_field_background_color_normal` instead. - """ - - input_field_text_color = ColorProperty(None) - """ - .. deprecated:: 1.1.0 - Use :attr:`input_field_text_color_normal` instead. - """ - - input_field_text_color_normal = ColorProperty(None) - """ - Text color normal of input fields in (r, g, b, a) or string format. - - .. versionadded:: 1.1.0 - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - text_button_color="lightgrey", - input_field_background_color_normal="brown", - input_field_background_color_focus="red", - input_field_text_color_normal="white", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png - :align: center - - :attr:`input_field_text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - input_field_text_color_focus = ColorProperty(None) - """ - Text color focus of input fields in (r, g, b, a) or string format. - - .. versionadded:: 1.1.0 - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - text_button_color="lightgrey", - input_field_background_color_normal="brown", - input_field_background_color_focus="red", - input_field_text_color_normal="white", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-field-text-color-normal-date.png - :align: center - - :attr:`input_field_text_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - font_name = StringProperty("Roboto") - """ - Font name for dialog window text. - - .. code-block:: python - - MDDatePicker( - primary_color="brown", - accent_color="darkred", - selector_color="red", - text_toolbar_color="lightgrey", - text_color="orange", - text_current_color="white", - text_button_color="lightgrey", - input_field_background_color_normal="brown", - input_field_background_color_focus="red", - input_field_text_color_normal="white", - input_field_text_color_focus="lightgrey", - font_name="nasalization.ttf", - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/font-name-date.png - :align: center - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_save") - self.register_event_type("on_cancel") - - def on_input_field_background_color( - self, instance, value: str | list | tuple - ) -> None: - """For supported of current API.""" - - self.input_field_background_color_normal = value - - def on_input_field_text_color( - self, instance, value: str | list | tuple - ) -> None: - """For supported of current API.""" - - self.input_field_text_color_normal = value - - def on_save(self, *args) -> None: - """Events called when the "OK" dialog box button is clicked.""" - - self.dismiss() - - def on_cancel(self, *args) -> None: - """Events called when the "CANCEL" dialog box button is clicked.""" - - self.dismiss() - - -class DatePickerBaseTooltip(MDTooltip): - """Implements tooltips for members of the :class:`~MDDatePicker` class.""" - - owner = ObjectProperty() # MDDatePicker object - hint_text = StringProperty() - - -class DatePickerIconTooltipButton(MDIconButton, DatePickerBaseTooltip): - pass - - -class DatePickerWeekdayLabel(MDLabel, DatePickerBaseTooltip): - pass - - -class DatePickerTypeDateError(Exception): - pass - - -class DatePickerInputField(MDTextField): - """ - Implements date input in dd/mm/yyyy format. - - For more information, see in the - :class:`~kivymd.uix.textfield.MDTextField` class documentation. - """ - - helper_text_mode = StringProperty("on_error") - owner = ObjectProperty() # MDDatePicker object - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.bind(text=self._on_text_check_errors) - - def _on_text_check_errors(self, widget, text): - if text == "": - self.error = False - return - try: - datetime.datetime.strptime(text, "%d/%m/%Y") - self.error = False - except ValueError: - self.error = True - - def set_error(self): - """Sets a text field to an error state.""" - - self.error = True - - def input_filter(self, value: str, boolean: bool) -> Union[str, None]: - """Filters the input according to the specified mode.""" - - if self.is_numeric(value): - return value - - def is_numeric(self, value: str) -> bool: - """ - Returns true if the value of the `value` argument can be converted - to an integer, or if the value of the `value` argument is '/'. - """ - - try: - if value == "/": - return True - int(value) - return True - except ValueError: - return False - - def get_list_date(self) -> list: - """ - Returns a list as `[dd, mm, yyyy]` from a text fied for entering a date. - """ - - return [d for d in self.text.split("/") if d] - - -class DatePickerInputFieldContainer(MDBoxLayout): - owner = ObjectProperty() # MDDatePicker object - - -class SelectYearList(FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout): - """A class that implements a list for choosing a year.""" - - -class DatePickerDaySelectableItem( - ThemableBehavior, CircularRippleBehavior, ButtonBehavior, AnchorLayout -): - """A class that implements a list for choosing a day.""" - - text = StringProperty() - owner = ObjectProperty() - is_today = BooleanProperty(False) - is_selected = BooleanProperty(False) - is_in_range = BooleanProperty(False) - is_range_start = BooleanProperty(False) - is_range_end = BooleanProperty(False) - is_month_end = BooleanProperty(False) - is_week_end = BooleanProperty(False) - - def on_release(self): - self.owner.set_selected_widget(self) - - def on_touch_down(self, touch): - # If year_layout is active don't dispatch on_touch_down events, - # so date items don't consume touch. - if not self.owner.ids._year_layout.disabled: - return - super().on_touch_down(touch) - - -class DatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel): - """Implements an item for a pick list of the year.""" - - index = None - selected = BooleanProperty(False) - owner = ObjectProperty() - - def refresh_view_attrs(self, rv, index, data): - self.index = index - return super().refresh_view_attrs(rv, index, data) - - def on_touch_down(self, touch): - if super().on_touch_down(touch): - return True - if self.collide_point(*touch.pos): - self.owner.year = int(self.text) - return self.parent.select_with_touch(self.index, touch) - - def apply_selection(self, table_data, index, is_selected): - self.selected = is_selected - - -# TODO: Add the feature to embed the `MDDatePicker` class in other layouts -# and not use it as a modal dialog. -# Add a date input mask. Currently, the date is entered in the format -# 'dd/mm/yy'. In some countries, the date is formatted as 'mm/dd/yy'. -class MDDatePicker(BaseDialogPicker): - text_weekday_color = ColorProperty(None) - """ - Text color of weekday names in (r, g, b, a) or string format. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-text-weekday-color.png - :align: center - - :attr:`text_weekday_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - helper_text = StringProperty("Wrong date") - """ - Helper text when entering an invalid date. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-date-picker-helper-text.png - :align: center - - :attr:`helper_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Wrong date'`. + :attr:`on_select_day` + Fired when a day is selected. + :attr:`on_select_month` + Fired when a month is selected. + :attr:`on_select_year` + Fired when a year is selected. + :attr:`on_cancel` + Fired when the 'Cancel' button is pressed. + :attr:`on_ok` + Fired when the 'Ok' button is pressed. + :attr:`on_edit` + Fired when you click on the date editing icon. + :attr:`on_dismiss` + Fired when a date picker closes. """ day = NumericProperty() @@ -794,8 +608,6 @@ class MDDatePicker(BaseDialogPicker): The day of the month to be opened by default. If not specified, the current number will be used. - See `Open date dialog with the specified date `_ for more information. - :attr:`day` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ @@ -805,8 +617,6 @@ class MDDatePicker(BaseDialogPicker): The number of month to be opened by default. If not specified, the current number will be used. - See `Open date dialog with the specified date `_ for more information. - :attr:`month` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ @@ -816,8 +626,6 @@ class MDDatePicker(BaseDialogPicker): The year of month to be opened by default. If not specified, the current number will be used. - See `Open date dialog with the specified date `_ for more information. - :attr:`year` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ @@ -842,10 +650,8 @@ class MDDatePicker(BaseDialogPicker): mode = OptionProperty("picker", options=["picker", "range"]) """ - Dialog type:`'picker'` type allows you to select one date; - `'range'` type allows to set a range of dates from which the - user can select a date. - Available options are: [`'picker'`, `'range'`]. + Dialog type. + Available options are: `'picker'`, `'range'`. :attr:`mode` is an :class:`~kivy.properties.OptionProperty` and defaults to `picker`. @@ -856,7 +662,6 @@ class MDDatePicker(BaseDialogPicker): The minimum value of the date range for the `'mode`' parameter. Must be an object . - See `Open date dialog with the specified date `_ for more information. :attr:`min_date` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. @@ -867,60 +672,74 @@ class MDDatePicker(BaseDialogPicker): The minimum value of the date range for the `'mode`' parameter. Must be an object . - See `Open date dialog with the specified date `_ for more information. - :attr:`max_date` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ - date_range_text_error = StringProperty("Error date range") + radius = VariableListProperty([dp(16)], length=4) """ - Error text that will be shown on the screen in the form of a toast if the - minimum date range exceeds the maximum. + Container radius. - :attr:`date_range_text_error` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Error date range'`. + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(16), dp(16), dp(16), dp(16)]`. """ - input_field_cls = ObjectProperty(DatePickerInputField) + scrim_color = ColorProperty([0, 0, 0, 0.5]) """ - A class that will implement date input in the format dd/mm/yyyy. - See :class:`~DatePickerInputField` class for more information. + Color for scrim in (r, g, b, a) or string format. - .. code-block:: python + :attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0.5]`. + """ - class CustomInputField(MDTextField): - owner = ObjectProperty() # required attribute + supporting_text = StringProperty("Select date") + """ + Supporting text. - # Required method. - def set_error(self): - [...] + :attr:`supporting_text` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Select date'`. + """ - # Required method. - def get_list_date(self): - [...] + text_button_ok = StringProperty("Ok") + """ + The text of the confirmation button. - # Required method. - def input_filter(self): - [...] + :attr:`text_button_ok` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Ok'`. + """ - def show_date_picker(self): - date_dialog = MDDatePicker(input_field_cls=CustomInputField) + text_button_cancel = StringProperty("Cancel") + """ + The text of the cancel button. - :attr:`input_field_cls` is an :class:`~kivy.properties.ObjectProperty` - and defaults to :class:`~DatePickerInputField`. + :attr:`text_button_cancel` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Cancel'`. + """ + + mark_today = BooleanProperty(True) + """ + Highlights the current day. + + :attr:`mark_today` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + + is_open = BooleanProperty(False) + """ + Is the date picker dialog open. + + :attr:`is_open` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. """ sel_year = NumericProperty() sel_month = NumericProperty() sel_day = NumericProperty() + calendar_layout = ObjectProperty() - _calendar_layout = ObjectProperty() _calendar_list = None - _fields_container = None - _scale_calendar_layout = NumericProperty(1) - _scale_year_layout = NumericProperty(0) - _shift_dialog_height = NumericProperty(0) + _current_month_name = StringProperty() + _current_full_month_name = StringProperty() _input_date_dialog_open = BooleanProperty(False) _select_year_dialog_open = False _date_label_text = StringProperty() @@ -942,15 +761,22 @@ class MDDatePicker(BaseDialogPicker): self.year = self.sel_year self.day = self.sel_day super().__init__(**kwargs) - self.theme_cls.bind(device_orientation=self.on_device_orientation) + self.register_event_type("on_select_day") + self.register_event_type("on_select_month") + self.register_event_type("on_select_year") + self.register_event_type("on_cancel") + self.register_event_type("on_ok") + self.register_event_type("on_edit") + self.register_event_type("on_dismiss") + self.opacity = 0 if self.max_date and self.min_date: if self.min_date and not isinstance(self.min_date, date): - raise DatePickerTypeDateError( + raise MDDatePickerTypeDateError( "'min_date' must be of class " ) if self.max_date and not isinstance(self.max_date, date): - raise DatePickerTypeDateError( + raise MDDatePickerTypeDateError( "'max_date' must be of class " ) self.compare_date_range() @@ -958,236 +784,106 @@ class MDDatePicker(BaseDialogPicker): self.generate_list_widgets_days() self.update_calendar(self.sel_year, self.sel_month) - def on_device_orientation( - self, instance_theme_manager: ThemeManager, orientation_value: str - ) -> None: - """Called when the device's screen orientation changes.""" - - # Separators of the label text depend on the orientation. - self._update_date_label_text() - if self._input_date_dialog_open: - if orientation_value == "portrait": - self._shift_dialog_height = dp(250) - if orientation_value == "landscape": - self._shift_dialog_height = dp(138) - - def on_ok_button_pressed(self) -> None: + def get_date(self, *args) -> list: """ - Called when the 'OK' button is pressed to confirm the date entered. + Returns a list of dates in the format + [datetime.date(yyyy, mm, dd), ...]. + The list has two dates if you use a date interval. """ - if self._input_date_dialog_open and not self._try_apply_input(): - return - self.dispatch( - "on_save", - date(self.sel_year, self.sel_month, self.sel_day), - self.get_date_range(), + if self.mode == "range": + return self._get_date_range() + else: + return [date(self.sel_year, self.sel_month, self.sel_day)] + + def set_text_full_date(self) -> str: + """Returns a string like "Tue, Feb 2".""" + + def date_repr(date): + return date.strftime("%b").capitalize() + " " + str(date.day) + + input_dates = ( + self._get_dates_from_fields() + if hasattr(self, "_get_dates_from_fields") + else None ) - - def is_date_valaid(self, date: str) -> bool: - """Checks the valid of the currently entered date.""" - - try: - time.strptime(date, "%d/%m/%Y") - return True - except ValueError: - return False - - def transformation_from_dialog_select_year(self) -> None: - self.ids.chevron_left.disabled = False - self.ids.chevron_right.disabled = False - self.ids._year_layout.disabled = True - self.ids.triangle.disabled = False - self._select_year_dialog_open = False - self.ids.triangle.icon = "menu-down" - - Animation(opacity=1, d=0.15).start(self.ids.chevron_left) - Animation(opacity=1, d=0.15).start(self.ids.chevron_right) - Animation(_scale_year_layout=0, d=0.15).start(self) - Animation(_scale_calendar_layout=1, d=0.15).start(self) - - # Move selection to the same day and month of the selected year. - self.sel_year = self.year - last_day = calendar.monthrange(self.year, self.sel_month)[1] - self.sel_day = min(self.sel_day, last_day) - self.update_calendar(self.year, self.month) - - def transformation_to_dialog_select_year(self) -> None: - def disabled_chevron_buttons(*args): - self.ids.chevron_left.disabled = True - self.ids.chevron_right.disabled = True - - self._select_year_dialog_open = True - self.ids._year_layout.disabled = False - Animation(opacity=0, d=0.15).start(self.ids.chevron_left) - Animation(opacity=0, d=0.15).start(self.ids.chevron_right) - Animation(_scale_calendar_layout=0, d=0.15).start(self) - anim = Animation(_scale_year_layout=1, d=0.15) - anim.bind(on_complete=disabled_chevron_buttons) - anim.start(self) - self.ids.triangle.icon = "menu-up" - self.generate_list_widgets_years() - self.set_position_to_current_year() - if self.min_year <= self.year < self.max_year: - index = self.year - self.min_year - self.ids._year_layout.children[0].select_node(index) - else: - self.ids._year_layout.children[0].clear_selection() - - def transformation_to_dialog_input_date(self) -> None: - self.ids.triangle.disabled = True - if self._select_year_dialog_open: - self.transformation_from_dialog_select_year() - self._input_date_dialog_open = True - self.ids.edit_icon.icon = "calendar" - self.ids.label_title.text = self.title_input - - self._fields_container = DatePickerInputFieldContainer(owner=self) if self.mode == "picker": selected_date = date(self.sel_year, self.sel_month, self.sel_day) - selected_dates = [selected_date] - else: - selected_dates = [self.min_date, self.max_date] - for selected_date in selected_dates: - field = self.get_field(selected_date) - field.bind(text=self._on_date_field_text_changes) - self._fields_container.add_widget(field) - self.ids.container.add_widget(self._fields_container) - - Animation( - _shift_dialog_height=dp(250) - if self.theme_cls.device_orientation == "portrait" - else dp(138), - _scale_calendar_layout=0, - d=0.15, - ).start(self) - Animation( - opacity=0, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0, - ).start(self.ids.chevron_left) - Animation( - opacity=0, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0, - ).start(self.ids.chevron_right) - Animation(opacity=0, d=0.15).start(self.ids.label_month_selector) - Animation(opacity=0, d=0.15).start(self.ids.triangle) - Animation(opacity=1, d=0.15).start(self._fields_container) - # The label text separator in landscape orientation depends on the - # open dialog. - self._update_date_label_text() - - def transformation_from_dialog_input_date( - self, interval: Union[int, float] - ) -> None: - if not self._try_apply_input(): - return - self._input_date_dialog_open = False - self.ids.triangle.disabled = False - self.ids.edit_icon.icon = "pencil" - self.ids.label_title.text = self.title - self.ids.container.remove_widget(self._fields_container) - self._fields_container = None - Animation( - _shift_dialog_height=dp(0), _scale_calendar_layout=1, d=0.15 - ).start(self) - Animation( - opacity=1, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0.65, - ).start(self.ids.chevron_left) - Animation( - opacity=1, - d=0.15 if self.theme_cls.device_orientation == "portrait" else 0.65, - ).start(self.ids.chevron_right) - Animation(opacity=1, d=0.15).start(self.ids.label_month_selector) - Animation(opacity=1, d=0.15).start(self.ids.triangle) - # The label text separator in landscape orientation depends on the - # open dialog. - self._update_date_label_text() - - def _get_dates_from_fields(self): - """ - Return a list of dates entered by the user in the input fields. - - If there is an error in the field or the field is empty, None will be - in its place in the list. The length of the list will be 0 if the input - dialog is closed, otherwise 1 in picker mode or 2 in range mode. - """ - - if not self._fields_container: - return [] - - dates = [] - # Widgets are arranged in the reverse order of their addition. - for field in reversed(self._fields_container.children): - try: - date = datetime.datetime.strptime(field.text, "%d/%m/%Y").date() - except ValueError: - date = None - dates.append(date) - - return dates - - def _try_apply_input(self) -> bool: - """ - Apply the dates entered by the user, update the calendar and return - True. If there are errors in the fields, do nothing and return False. - """ - - dates = self._get_dates_from_fields() - if not dates: - return True - - # Widgets are arranged in the reverse order of their addition. - fields = reversed(self._fields_container.children) - if any(d is None and f.text for f, d in zip(fields, dates)): - return False - - if self.mode == "picker": - selected_date = date(self.sel_year, self.sel_month, self.sel_day) - selected_date = dates[0] or selected_date - self.sel_year = selected_date.year - self.sel_month = selected_date.month - self.sel_day = selected_date.day - self.update_calendar(self.sel_year, self.sel_month) - elif self.mode == "range": - date1, date2 = dates[0] or self.min_date, dates[1] or self.max_date - ends = list(filter(bool, [date1, date2])) - if ends: - self.min_date = min(ends) - self.max_date = max(ends) - self.update_calendar(self.year, self.month) - - return True - - def _on_date_field_text_changes(self, *args): - self._update_date_label_text() + if input_dates: + selected_date = input_dates[0] or selected_date + weekday_repr = selected_date.strftime("%a").capitalize() + separator = ", " + return weekday_repr + separator + date_repr(selected_date) + elif self.mode == "range" and self.min_date and self.max_date: + start, end = self.min_date, self.max_date + if input_dates: + start, end = input_dates[0] or start, input_dates[1] or end + ends = [end for end in (start, end) if end] + if len(ends) == 0: + start_repr, end_repr = "Start", "End" + else: + start, end = min(ends), max(ends) + start_repr, end_repr = date_repr(start), date_repr(end) + separator = " — " + return start_repr + separator + end_repr + elif self.mode == "range" and not self.min_date and not self.max_date: + dates = input_dates + if not dates: + return "Start - End" + elif len(dates) == 1: + return ( + f"{date_repr(dates[0])}, " + f"{dates[0].strftime('%Y')} - " + f"End" + ) + elif len(dates) == 2: + return ( + f"{date_repr(dates[0])}, {dates[0].strftime('%Y')} - " + f"{date_repr(dates[1])}, {dates[1].strftime('%Y')}" + ) def compare_date_range(self) -> None: # TODO: Add behavior if the minimum date range exceeds the maximum # date range. Use toast? if self.max_date <= self.min_date: - raise DatePickerTypeDateError( + raise MDDatePickerTypeDateError( "`max_date` value cannot be less than or equal " "to 'min_date' value" ) - def update_calendar_for_date_range(self) -> None: - # This method is no longer used, use update_calendar instead. - self.update_calendar(self.year, self.month) - - def update_text_full_date(self, list_date) -> None: + def change_month(self, operation: str) -> None: """ - Updates the title of the week, month and number day name - in an open date input dialog. + Called when "chevron-left" and "chevron-right" buttons are pressed. + Switches the calendar to the previous/next month. """ - # This method no longer used, use update_calendar instead. - year = int(list_date[2]) if len(list_date) > 2 else self.sel_year - month = int(list_date[1]) if len(list_date) > 1 else self.sel_month - day = int(list_date[0]) if len(list_date) > 0 else self.sel_day - day = min(day, calendar.monthrange(year, month)[1]) - self.sel_year, self.sel_month, self.sel_day = year, month, day + month_delta = 1 if operation == "next" else -1 + year = self.year + (self.month - 1 + month_delta) // 12 + month = (self.month - 1 + month_delta) % 12 + 1 + + if year <= 0: + year, month = 1, 1 self.update_calendar(year, month) + self.dispatch("on_select_month", month) + + def generate_list_widgets_days(self) -> None: + calendar_list = [] + + for day in self.calendar.iterweekdays(): + weekday_label = MDDatePickerWeekdayLabel( + text=calendar.day_name[day][0].upper(), + date_picker=self, + ) + self.calendar_layout.add_widget(weekday_label) + for i in range(6 * 7): # 6 weeks, 7 days a week + day_selectable_item = MDDatePickerDaySelectableItem( + is_week_end=i % 7 == 6, + date_picker=self, + ) + calendar_list.append(day_selectable_item) + self.calendar_layout.add_widget(day_selectable_item) + + self._calendar_list = calendar_list def update_calendar(self, year, month) -> None: self.year, self.month = year, month @@ -1196,10 +892,12 @@ class MDDatePicker(BaseDialogPicker): selected_dates = {selected_date} else: selected_dates = {self.min_date, self.max_date} + # The label text depends on the selected date or date range. self._update_date_label_text() month_end = date(year, month, calendar.monthrange(year, month)[1]) dates = self.calendar.itermonthdates(year, month) + for widget, widget_date in zip_longest(self._calendar_list, dates): # Only widgets whose dates are in the displayed month are visible. visible = ( @@ -1209,11 +907,11 @@ class MDDatePicker(BaseDialogPicker): ) widget.text = str(widget_date.day) if visible else "" widget.is_today = visible and widget_date == self.today - widget.is_selected = visible and widget_date in selected_dates - # I don't understand why, but this line is important. Without this - # line, some widgets that we are trying to disable remain enabled. - widget.disabled = False - widget.disabled = not visible + widget.is_selected = visible and (widget_date in selected_dates) + + if self.min_date and self.max_date and self.mode != "range": + widget.disabled = True + widget.is_in_range = ( visible and self.min_date is not None @@ -1232,116 +930,25 @@ class MDDatePicker(BaseDialogPicker): ) widget.is_month_end = widget_date == month_end - def get_field(self, date=None) -> MDTextField: - """Creates and returns a text field object used to enter dates.""" - - if issubclass(self.input_field_cls, MDTextField): - text_color_focus = ( - self.input_field_text_color_focus - if self.input_field_text_color_focus - else self.theme_cls.primary_color - ) - text_color_normal = ( - self.input_field_text_color_normal - if self.input_field_text_color_normal - else self.theme_cls.disabled_hint_text_color - ) - fill_color_focus = ( - self.input_field_background_color_focus - if self.input_field_background_color_focus - else self.theme_cls.bg_dark - ) - fill_color_normal = ( - self.input_field_background_color_normal - if self.input_field_background_color_normal - else self.theme_cls.bg_darkest - ) - - field = self.input_field_cls( - owner=self, - text=date.strftime("%d/%m/%Y") if date else "", - helper_text=self.helper_text, - fill_color_normal=fill_color_normal, - fill_color_focus=fill_color_focus, - hint_text_color_normal=text_color_normal, - hint_text_color_focus=text_color_focus, - text_color_normal=text_color_normal, - text_color_focus=text_color_focus, - line_color_focus=text_color_focus, - line_color_normal=text_color_normal, - ) - return field - else: - raise TypeError( - "The `input_field_cls` parameter must be an object of the " - "`kivymd.uix.textfield.MDTextField class`" - ) - - def get_date_range(self) -> list: - if not self.min_date or not self.max_date: - return [] - date_range = [ - self.min_date + datetime.timedelta(days=x) - for x in range((self.max_date - self.min_date).days + 1) - ] - return date_range - - def set_text_full_date(self, year, month, day, orientation): - """ - Returns a string of type "Tue, Feb 2" or "Tue,\nFeb 2" for a date - choose and a string like "Feb 15 - Mar 23" or "Feb 15,\nMar 23" for - a date range. - """ - - # In portrait orientation, the label is stretched in width, so we - # should not insert line breaks. When the input dialog is open, the - # label moves to the right and also stretches in width. - horizontal = orientation == "portrait" or self._input_date_dialog_open - - def date_repr(date): - return date.strftime("%b").capitalize() + " " + str(date.day) - - input_dates = self._get_dates_from_fields() - if self.mode == "picker": - selected_date = date(self.sel_year, self.sel_month, self.sel_day) - if input_dates: - selected_date = input_dates[0] or selected_date - weekday_repr = selected_date.strftime("%a").capitalize() - separator = ", " if horizontal else ",\n" - return weekday_repr + separator + date_repr(selected_date) - elif self.mode == "range": - start, end = self.min_date, self.max_date - if input_dates: - start, end = input_dates[0] or start, input_dates[1] or end - ends = [end for end in (start, end) if end] - if len(ends) == 0: - start_repr, end_repr = "Start", "End" - else: - start, end = min(ends), max(ends) - start_repr, end_repr = date_repr(start), date_repr(end) - separator = " — " if horizontal else ",\n" - return start_repr + separator + end_repr - - def _update_date_label_text(self): - self._date_label_text = self.set_text_full_date( - self.sel_year, - self.sel_month, - self.sel_day, - self.theme_cls.device_orientation, - ) + if self.min_date and self.max_date and self.mode != "range": + if widget.is_in_range: + widget.disabled = False def set_selected_widget(self, widget) -> None: if self._select_year_dialog_open or self._input_date_dialog_open: return + try: widget_date = date(self.year, self.month, int(widget.text)) except ValueError: return + if self.mode == "picker": self.sel_year = widget_date.year self.sel_month = widget_date.month self.sel_day = widget_date.day self.update_calendar(self.sel_year, self.sel_month) + self.dispatch("on_select_day", widget_date.day) elif self.mode == "range": ends = [end for end in (self.min_date, self.max_date) if end] if widget_date in ends: @@ -1361,84 +968,1004 @@ class MDDatePicker(BaseDialogPicker): self.min_date, self.max_date = min(ends), max(ends) self.update_calendar(self.year, self.month) - def set_month_day(self, day) -> None: - # This method is no longer used. The code bellow repeats the behavior - # that was previously required of it for backward compatibility - # reasons. - self.sel_day = day - self.update_calendar(self.sel_year, self.sel_month) + def restore_calendar_layout_properties(self) -> None: + self.ids.calendar_layout.padding = [dp(12), 0, dp(12), 0] + Animation(opacity=1, d=0.2, t="out_quad").start( + self.ids.calendar_layout + ) - def set_position_to_current_year(self) -> None: - year_layout = self.ids._year_layout - # When this method is called for the first time, RecycleView has not - # yet added widgets to the year list, so we use the default height. - widget_height = year_layout.children[0].default_size[1] - cols_amount = year_layout.children[0].cols - rows_amount = math.ceil((self.max_year - self.min_year) / cols_amount) - row_index = (self.year - self.min_year) // cols_amount - # To find the middle of the current year widget, we add the height of - # the rows under this widget with half the widget height. - widget_center_y = (rows_amount - row_index - 1 + 0.5) * widget_height - viewport_height = year_layout.height - year_list_height = rows_amount * widget_height - # If there are too few years in the list to fill the entire viewport, - # RecycleView displays additional empty space outside the list. - # We have to move the viewport up so that this space is displayed - # under the years list. Also, this guard condition protects against - # the division by zero error below. - if viewport_height >= year_list_height: - year_layout.scroll_y = 1 + def set_calendar_layout_properties(self, method) -> None: + anim = Animation( + padding=[dp(18), 0, 0, 0], opacity=0, d=0.2, t="in_quad" + ) + anim.bind(on_complete=method) + anim.start(self.ids.calendar_layout) + + def dismiss(self, *args) -> None: + """Dismiss the dialog date picker.""" + + super().on_dismiss() + self.is_open = False + + def open(self) -> None: + """Show the dialog date picker.""" + + if not self.is_open: + Window.add_widget(self) + super().on_open() + self.is_open = True + + def on_touch_down(self, touch): + if not self.collide_point(*touch.pos): + self.dismiss() + return True + super().on_touch_down(touch) + return True + + def on_select_day(self, *args) -> None: + """Fired when a day is selected.""" + + def on_select_month(self, *args) -> None: + """Fired when a month is selected.""" + + def on_select_year(self, *args) -> None: + """Fired when a year is selected.""" + + def on_cancel(self, *args) -> None: + """Fired when the 'Cancel' button is pressed.""" + + def on_ok(self, *args) -> None: + """Fired when the 'Ok' button is pressed.""" + + def on_edit(self, *args) -> None: + """Fired when you click on the date editing icon.""" + + def on_dismiss(self, *args) -> None: + """Fired when a date picker closes.""" + + def _update_date_label_text(self): + self._current_month_name = ( + date(self.sel_year, self.sel_month, self.sel_day) + .strftime("%b") + .capitalize() + ) + + def _get_date_range(self) -> list: + if not self.min_date or not self.max_date: + return [] + date_range = [ + self.min_date + datetime.timedelta(days=x) + for x in range((self.max_date - self.min_date).days + 1) + ] + return date_range + + +class MDDatePickerButtonsContainer(BoxLayout): + """ + Implements a container with buttons for date picker dialogs. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + +class MDDatePickerBaseMenuSelectionButton( + RotateBehavior, ButtonBehavior, MDIcon +): + """ + Implements a button to switch the month/year selection menu for + :class:`~MDDatePickerMonthSelectionButton` and + :class:`~MDDatePickerYearSelectionButton` classes. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.label.label.MDIcon` + classes documentation. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + +class MDDatePickerWeekdayLabel(MDLabel): + """ + Implements labels for the names of the days of the week. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + +class MDCalendarLayout(ScaleBehavior, ThemableBehavior, GridLayout): + """ + Implements a grid for calendar dates. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivymd.uix.behaviors.ThemableBehavior` and + :class:`~kivy.uix.gridlayout.GridLayout` + classes documentation. + """ + + +class MDDatePickerDaySelectableItem( + CircularRippleBehavior, ButtonBehavior, MDLabel +): + """ + Implements an element for a grid of calendar dates. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.label.label.MDLabel` + classes documentation. + """ + + date_picker = ObjectProperty() + is_today = BooleanProperty(False) + is_selected = BooleanProperty(False) + is_in_range = BooleanProperty(False) + is_range_start = BooleanProperty(False) + is_range_end = BooleanProperty(False) + is_month_end = BooleanProperty(False) + is_week_end = BooleanProperty(False) + + def on_release(self): + self.date_picker.set_selected_widget(self) + + +############################################################################### +# +# DOCKED CLASSES +# +############################################################################### + + +class MDDockedDatePickerBaseSelectionContainer(BoxLayout): + """ + Implements a basic container for switching month/year items and selecting + the month/year list menu. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.boxlayout.BoxLayout` class documentation. + """ + + text = StringProperty() + """ + The current name of the month or the current year. + + :attr:`text` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.register_event_type("on_release") + self.register_event_type("on_open_menu") + + def on_open_menu(self, *args) -> None: + """Fired when the 'menu-right' button are pressed.""" + + def on_release(self, *args) -> None: + """ + Fired when the 'chevron-left' and `chevron-right` buttons are pressed. + """ + + +class MDDockedDatePickerMonthSelectionItem( + MDDockedDatePickerBaseSelectionContainer +): + """ + Implements a container with buttons for switching months and selecting + the month list menu. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~MDDockedDatePickerBaseSelectionContainer` class documentation. + """ + + def on_open_menu(self, *args) -> None: + """Fired when the 'menu-right' button are pressed.""" + + if not self.date_picker._menu_month_year_selection_is_open: + self.date_picker.generate_menu_month_year_selection( + menu_type="month" + ) + self.date_picker.open_close_menu_month_year_selection( + state=True, menu_type="month" + ) + self.date_picker._menu_month_year_selection_is_open = True + else: + self.date_picker.open_close_menu_month_year_selection( + state=False, menu_type="month" + ) + self.date_picker._menu_month_year_selection_is_open = False + + def on_release(self, direction: str) -> None: + """ + Fired when the 'chevron-left' and chevron-right buttons are pressed. + """ + + def set_month(*args): + self.date_picker.change_month(direction) + self.date_picker._update_date_label_text() + self.date_picker.restore_calendar_layout_properties() + + self.date_picker.set_calendar_layout_properties(set_month) + + +class MDDockedDatePickerYearSelectionItem( + MDDockedDatePickerBaseSelectionContainer +): + """ + Implements a container with buttons for switching years and selecting + the year list menu. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~MDDockedDatePickerBaseSelectionContainer` class documentation. + """ + + def on_open_menu(self, *args) -> None: + """Fired when the 'menu-right' button are pressed.""" + + if not self.date_picker._menu_month_year_selection_is_open: + self.date_picker.generate_menu_month_year_selection( + menu_type="year" + ) + self.date_picker.open_close_menu_month_year_selection( + state=True, menu_type="year" + ) + self.date_picker._menu_month_year_selection_is_open = True + else: + self.date_picker.open_close_menu_month_year_selection( + state=False, menu_type="year" + ) + self.date_picker._menu_month_year_selection_is_open = False + + def on_release(self, direction: str) -> None: + """ + Fired when the 'chevron-left' and chevron-right buttons are pressed. + """ + + def set_year(*args): + self.date_picker.update_calendar(year, self.date_picker.month) + self.date_picker.restore_calendar_layout_properties() + + year = self.date_picker.year + if direction == "prev": + year -= 1 + elif direction == "next": + year += 1 + + if year < self.date_picker.min_year or year > self.date_picker.max_year: + raise MDDatePickerTypeDateError( + "The maximum/minimum value of the year is exceeded or less " + "than the set value" + ) return - viewport_bottom = widget_center_y - 0.5 * viewport_height - # We set scroll_y property to the ratio of the actual lifting height - # of the viewport to the maximum possible, and clamp this ratio in the - # range from 0 to 1 so that the viewport still is in a valid position - # if it is impossible to show the widget in the middle. - scroll_y = viewport_bottom / (year_list_height - viewport_height) - year_layout.scroll_y = min(1, max(0, scroll_y)) + + self.date_picker.set_calendar_layout_properties(set_year) + + +class MDDockedDatePickerMenuMonthYearSelection(ScaleBehavior, RecycleView): + """ + Implements a menu with a list of months/years. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivy.uix.recycleview.RecycleView` and + classes documentation. + """ + + +class MDDockedDatePickerContainerMenuMonthYearSelection( + FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout +): + """ + Implements a container for the month/year list menu. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.behaviors.FocusBehavior` and + :class:`~kivy.uix.recycleview.layout.LayoutSelectionBehavior and + :class:`~kivy.uix.recycleboxlayout.RecycleBoxLayout and + classes documentation. + """ + + +class MDDockedDatePickerMenuSelectionItem(RecycleDataViewBehavior, BoxLayout): + """ + Implements an item for the month selection list menu. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.recycleview.views.RecycleDataViewBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + """ + + index = None + selected = BooleanProperty(False) + selectable = BooleanProperty(True) + + month_index = NumericProperty() + """ + Month index. + + :attr:`month_index` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0`. + """ + + month_year_name = StringProperty() + """ + Full name of the month. + + :attr:`month_year_name` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def refresh_view_attrs(self, rv, index, data): + self.index = index + return super().refresh_view_attrs(rv, index, data) + + def on_touch_down(self, touch): + if super().on_touch_down(touch): + return True + if self.collide_point(*touch.pos) and self.selectable: + if self.menu_type == "month": + self.date_picker.update_calendar( + self.date_picker.year, self.month_index + ) + self.date_picker.dispatch("on_select_month", self.month_index) + else: + self.date_picker.update_calendar( + int(self.month_year_name), self.date_picker.month + ) + self.date_picker.dispatch( + "on_select_year", int(self.month_year_name) + ) + return self.parent.select_with_touch(self.index, touch) + + def apply_selection(self, rv, index, is_selected): + self.selected = is_selected + rv.data[index]["selected"] = is_selected + + +class MDDockedDatePicker(CommonElevationBehavior, MDBaseDatePicker): + """ + Implements docked date picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~MDBaseDatePicker` + classes documentation. + """ + + _menu_month_year_selection_is_open = BooleanProperty(False) + + def generate_menu_month_year_selection( + self, menu_type: str = "month" + ) -> None: + """Generates a list for the month or year selection menu.""" + + self.ids.month_year_selection_layout.data = [] + + if menu_type == "month": + for month in range(1, 13): + month_name = datetime.date(1, month, 1).strftime("%B") + self.ids.month_year_selection_layout.data.append( + { + "height": dp(48), + "date_picker": self, + "menu_type": menu_type, + "month_year_name": month_name, + "month_index": month, + "viewclass": "MDDockedDatePickerMenuSelectionItem", + } + ) + else: + for year_name in range(self.min_year, self.max_year): + self.ids.month_year_selection_layout.data.append( + { + "height": dp(48), + "date_picker": self, + "menu_type": menu_type, + "month_year_name": str(year_name), + "month_index": 0, + "viewclass": "MDDockedDatePickerMenuSelectionItem", + } + ) + + def open_close_menu_month_year_selection( + self, state: bool = True, menu_type: str = "month" + ) -> None: + """ + Hides the calendar layout and opens the list to select the month or + year. + """ + + opacity = 0 if state else 1 + scale = 1 if state else 0 + disabled = True if state else 0 + rotate = -90 if state else 0 + + month_selection_items = self.ids.month_selection_items + year_selection_items = self.ids.year_selection_items + calendar_layout = self.ids.calendar_layout + button_container = self.ids.button_container + month_year_selection_layout = self.ids.month_year_selection_layout + + Animation(rotate_value_angle=rotate, d=0.2).start( + month_selection_items.ids.menu_selection_button + if menu_type == "month" + else self.ids.year_selection_items.ids.menu_selection_button + ) + + month_selection_items.ids.chevron_left.disabled = disabled + month_selection_items.ids.chevron_left.opacity = opacity + month_selection_items.ids.chevron_right.disabled = disabled + month_selection_items.ids.chevron_right.opacity = opacity + + year_selection_items.ids.chevron_left.disabled = disabled + year_selection_items.ids.chevron_left.opacity = opacity + year_selection_items.ids.chevron_right.disabled = disabled + year_selection_items.ids.chevron_right.opacity = opacity + + calendar_layout.disabled = disabled + calendar_layout.opacity = opacity + if state: + calendar_layout.scale_value_x = 1.1 + calendar_layout.scale_value_y = 1.1 + + if menu_type == "month": + year_selection_items.ids.label.disabled = disabled + year_selection_items.ids.menu_selection_button.opacity = opacity + else: + month_selection_items.ids.label.disabled = disabled + month_selection_items.ids.menu_selection_button.opacity = opacity + + month_year_selection_layout.size = ( + ( + self.width, + self.height + - ( + self.ids.month_year_selection_items_container.height + + dp(56) + ), + ) + if state + else (0, 0) + ) + + button_container.opacity = opacity + button_container.disabled = disabled + + Animation(scale_value_x=scale, scale_value_y=scale, d=0.2).start( + month_year_selection_layout + ) + if not state: + Animation(scale_value_x=1, scale_value_y=1, d=0.1).start( + calendar_layout + ) + + +############################################################################### +# +# MODAL CLASSES +# +############################################################################### + + +class MDModalDatePicker(CommonElevationBehavior, MDBaseDatePicker): + """ + Implements modal date picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~MDBaseDatePicker` + classes documentation. + """ + + _menu_year_selection_is_open = BooleanProperty(False) + + def open(self) -> None: + """Show the dialog date picker.""" + + if not self.is_open: + if not self._scrim: + self._scrim = MDModalDatePickerScrim(color=self.scrim_color) + + Window.add_widget(self._scrim) + Window.add_widget(self) + super().on_open() + self.is_open = True def generate_list_widgets_years(self) -> None: - self.ids._year_layout.data = [] + self.ids.year_selection_layout.data = [] for i, number_year in enumerate(range(self.min_year, self.max_year)): - self.ids._year_layout.data.append( + self.ids.year_selection_layout.data.append( { - "owner": self, + "date_picker": self, "text": str(number_year), "index": i, - "viewclass": "DatePickerYearSelectableItem", + "viewclass": "MDModalDatePickerYearSelectableItem", } ) + def open_menu_year_selection(self, *args) -> None: + self._menu_year_selection_is_open = ( + not self._menu_year_selection_is_open + ) + state = self._menu_year_selection_is_open + opacity = 0 if state else 1 + disabled = True if state else 0 + rotate = -90 if state else 0 + + calendar_layout = self.ids.calendar_layout + year_selection_items = self.ids.year_selection_items + year_selection_layout = self.ids.year_selection_layout + chevron_left = year_selection_items.ids.chevron_left + chevron_right = year_selection_items.ids.chevron_right + chevron_left.disabled = disabled + chevron_left.opacity = opacity + chevron_right.disabled = disabled + chevron_right.opacity = opacity + calendar_layout.disabled = disabled + calendar_layout.opacity = opacity + + Animation(rotate_value_angle=rotate, d=0.2).start( + self.ids.year_selection_items.ids.menu_selection_button + ) + + year_selection_layout.size = ( + (calendar_layout.width - dp(36), calendar_layout.height) + if state + else (0, 0) + ) + + if state: + year_selection_layout.disabled = False + calendar_layout.scale_value_x = 1.1 + calendar_layout.scale_value_y = 1.1 + Animation( + scale_value_x=1, scale_value_y=1, t="out_expo", d=0.4 + ).start(year_selection_layout) + else: + Animation(scale_value_x=1, scale_value_y=1, d=0.1).start( + calendar_layout + ) + year_selection_layout.scale_value_x = 1.2 + year_selection_layout.scale_value_y = 1.2 + year_selection_layout.disabled = True + + def _update_date_label_text(self): + self._current_month_name = self.set_text_full_date() + self._current_full_month_name = ( + f'{datetime.date(1, self.month, 1).strftime("%B")} {self.year}' + ) + + +class MDModalDatePickerYearSelectableItem(RecycleDataViewBehavior, MDLabel): + """Implements an item for a pick list of the year.""" + + index = None + selected = BooleanProperty(False) + selectable = BooleanProperty(True) + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def refresh_view_attrs(self, rv, index, data): + self.index = index + return super().refresh_view_attrs(rv, index, data) + + def on_touch_down(self, touch): + if super().on_touch_down(touch): + return True + if self.collide_point(*touch.pos): + self.date_picker.update_calendar( + int(self.text), self.date_picker.sel_month + ) + self.date_picker.dispatch("on_select_year", int(self.text)) + return self.parent.select_with_touch(self.index, touch) + + def apply_selection(self, rv, index, is_selected): + self.selected = is_selected + rv.data[index]["selected"] = is_selected + + +class MDModalDatePickerYearSelectionItem(ScaleBehavior, BoxLayout): + """ + Implements a container for switching month items. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivymd.uix.boxlayout.BoxLayout` + classes documentation. + """ + + text = StringProperty() + """ + The current name of the month or the current year. + + :attr:`text` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.register_event_type("on_release") + self.register_event_type("on_open_menu") + + def on_open_menu(self, *args) -> None: + """Fired when the 'menu-right' button are pressed.""" + + self.date_picker.open_menu_year_selection() + self.date_picker.generate_list_widgets_years() + + def on_release(self, direction: str) -> None: + """ + Fired when the 'chevron-left' and `chevron-right` buttons are pressed. + """ + + def set_month(*args): + self.date_picker.change_month(direction) + self.date_picker._update_date_label_text() + self.date_picker.restore_calendar_layout_properties() + + self.date_picker.set_calendar_layout_properties(set_month) + + +class MDModalDatePickerScrim(Widget): + """ + Implements scrim for the modal date picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.widget.Widget` and class documentation. + """ + + color = ColorProperty(None) + alpha = NumericProperty(0) + + +class MDModalDatePickerMenuYearSelection(ScaleBehavior, RecycleView): + """ + Implements a menu with a list of years. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivy.uix.recycleview.RecycleView` and + classes documentation. + """ + + +class MDModalDatePickerContainerMenuYearSelection( + FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout +): + """ + Implements a container for the year list menu. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.behaviors.FocusBehavior` and + :class:`~kivy.uix.recycleview.layout.LayoutSelectionBehavior and + :class:`~kivy.uix.recyclegridlayout.RecycleGridLayout and + classes documentation. + """ + + +############################################################################### +# +# MODAL INPUT CLASSES +# +############################################################################### + + +class MDModalInputDatePicker(CommonElevationBehavior, MDBaseDatePicker): + """ + Implements modal input date picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~MDBaseDatePicker` + classes documentation. + """ + + date_format = OptionProperty( + "mm/dd/yyyy", + options=[ + "dd/mm/yyyy", + "mm/dd/yyyy", + "yyyy/mm/dd", + ], + ) + + """ + Format of date strings that will be entered. + Available options are: `'dd/mm/yyyy'`, `'mm/dd/yyyy'`, `'yyyy/mm/dd'`. + + :attr:`date_format` is an :class:`~kivy.properties.OptionProperty` + and defaults to `None`. + """ + + default_input_date = BooleanProperty(True) + """ + If true, the current date will be set in the input field. + + :attr:`default_input_date` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + + error_text = StringProperty("Invalid date format") + """ + Error text when the date entered by the user is not valid. + + :attr:`error_text` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Invalid date format'`. + """ + + supporting_input_text = StringProperty("Enter date") + """ + Auxiliary text when entering the date manually. + + :attr:`supporting_input_text` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Enter date'`. + """ + + _date_format_strftime = { + "dd/mm/yyyy": "%d/%m/%Y", + "mm/dd/yyyy": "%m/%d/%Y", + "yyyy/mm/dd": "%Y/%m/%d", + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + Clock.schedule_once(self._check_range, 0.1) + def generate_list_widgets_days(self) -> None: - calendar_list = [] + ... - for day in self.calendar.iterweekdays(): - weekday_label = DatePickerWeekdayLabel( - text=calendar.day_name[day][0].upper(), - owner=self, - hint_text=calendar.day_name[day], - ) - weekday_label.font_name = self.font_name - self._calendar_layout.add_widget(weekday_label) - for i in range(6 * 7): # 6 weeks, 7 days a week - day_selectable_item = DatePickerDaySelectableItem( - is_week_end=i % 7 == 6, - owner=self, - ) - calendar_list.append(day_selectable_item) - self._calendar_layout.add_widget(day_selectable_item) - self._calendar_list = calendar_list + def update_calendar(self, *args) -> None: + ... - def change_month(self, operation: str) -> None: + def set_input_date(self, input_date: str) -> None: + split_date = [d for d in input_date.split("/") if d] + mask_date = self.date_format + split_mask_date = mask_date.split("/") + + month_index = split_mask_date.index("mm") + day_index = split_mask_date.index("dd") + year_index = split_mask_date.index("yyyy") + + self.sel_month = int(split_date[month_index]) + self.sel_day = int(split_date[day_index]) + self.sel_year = int(split_date[year_index]) + + month = self.sel_month + day = self.sel_day + year = self.sel_year + full_month_name = datetime.datetime(year, month, day).strftime( + "%d %B, %Y" + ) + + self._current_month_name = self.set_text_full_date() + self._current_full_month_name = f"{full_month_name} {year}" + + def get_date(self, *args) -> list: """ - Called when "chevron-left" and "chevron-right" buttons are pressed. - Switches the calendar to the previous/next month. + Returns a list of dates in the format + [datetime.date(yyyy, mm, dd), ...]. + The list has two dates if you use a date interval. """ - month_delta = 1 if operation == "next" else -1 - year = self.year + (self.month - 1 + month_delta) // 12 - month = (self.month - 1 + month_delta) % 12 + 1 + return self._get_dates_from_fields() - if year <= 0: - year, month = 1, 1 - self.update_calendar(year, month) + def get_current_date_from_format(self) -> str: + """ + Returns the date according to the set format in :attr:`date_format`. + """ + + date_from_format = "" + data_date = { + "mm": self.sel_month, + "dd": self.sel_day, + "yyyy": self.sel_year, + } + split_mask_date = self.date_format.split("/") + for mask in split_mask_date: + date_from_format += f"{str(data_date[mask])}/" + + return date_from_format[:-1] + + def open(self) -> None: + """Show the dialog date picker.""" + + if not self.is_open: + if not self._scrim: + self._scrim = MDModalDatePickerScrim(color=self.scrim_color) + + Window.add_widget(self._scrim) + Window.add_widget(self) + super().on_open() + self.is_open = True + + def _set_current_date(self): + self.ids.input_date_field.text = self.today.strftime( + self._date_format_strftime[self.date_format] + ) + + def _check_range(self, *args): + if self.mode == "picker": + self._set_current_date() + return + + field_start = self.ids.input_date_field + + if self.min_date: + field_start.text = self.min_date.strftime( + self._date_format_strftime[self.date_format] + ) + + if self.max_date or self.mode == "range": + input_date_container = self.ids.input_date_container + input_date_container.spacing = "12dp" + + field_end = MDModalInputDatePickerInputField( + MDTextFieldHintText( + text=self.date_format, + ), + MDTextFieldHelperText( + text=self.error_text, + mode="on_error", + ), + id="fffffff", + date_picker=self, + date_format=self.date_format, + text=self.max_date.strftime( + self._date_format_strftime[self.date_format] + ) + if self.max_date + else "", + ) + input_date_container.add_widget(field_end) + + def _get_dates_from_fields(self) -> list: + dates = [] + # Widgets are arranged in the reverse order of their addition. + for field in reversed(self.ids.input_date_container.children): + try: + dates.append( + datetime.datetime.strptime( + field.text, + self._date_format_strftime[self.date_format], + ).date() + ) + except ValueError: + pass + + return dates + + +class MDModalInputDatePickerInputDateFieldContainer(BoxLayout): + """ + Implements a text field for entering the date manually. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + +class MDModalInputDatePickerInputField(MDTextField): + """ + Implements date input. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.textfield.textfield.MDTextField` class documentation. + """ + + date_picker: MDBaseDatePicker = ObjectProperty() + """ + Date picker object - + + :attr:`date_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + def on_error(self, instance, value) -> None: + """Fired when the `error` value changes.""" + + super().on_error(instance, value) + + if not value: + self.date_picker.set_input_date(self.text) + else: + self.date_picker._current_month_name = ( + self.date_picker.supporting_input_text + ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__init__.py index 8e776c3..dc416f8 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__init__.py @@ -1 +1,5 @@ -from .timepicker import MDTimePicker # NOQA F401 +from .timepicker import ( + MDTimePickerDialVertical, + MDTimePickerDialHorizontal, + MDTimePickerInput, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/__init__.cpython-311.pyc index bd0d8af..ec5a6ef 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/timepicker.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/timepicker.cpython-311.pyc index 1eba59e..c62f8b7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/timepicker.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/__pycache__/timepicker.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.kv index 7844326..4edec2e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.kv @@ -1,132 +1,275 @@ -: - theme_text_color: "Custom" - font_size: dp(10) - halign: "left" - valign: "bottom" - adaptive_size: True +############################################################################### +# +# INPUT RULES +# +############################################################################### - - - halign: "center" - valign: "center" - theme_text_color: "Custom" - - - + size_hint: None, None + size: dp(328), dp(248) + _time_input: _time_input + # _selector: _selector + _am_pm_selector: _am_pm_selector + MDLabel: + adaptive_size: True + text: root.headline_text + font_style: "Label" + role: "large" + padding: 0, 0, 0, dp(20) + + BoxLayout: + size_hint_y: None + height: self.minimum_height + spacing: dp(20) + + BoxLayout: + orientation: "vertical" + size_hint_y: None + height: self.minimum_height + + MDTimePickerInputContainer: + id: _time_input + time_picker: root + _readonly: False + # root._get_time_input(*self.get_time()) + + BoxLayout: + size_hint_y: None + height: self.minimum_height + padding: 0, dp(8), 0, 0 + + MDLabel: + role: "small" + adaptive_height: True + text: "Hour" + + MDLabel: + role: "small" + adaptive_height: True + text: "Minute" + padding_x: dp(20) + + BoxLayout: + size_hint: None, None + size: self.minimum_size + padding: 0, 0, 0, dp(26) + + MDTimePickerAmPmSelector: + id: _am_pm_selector + size_hint: None, None + size: dp(52), dp(80) + on_selected: + root.dispatch("on_am_pm", self.selected) + root._get_am_pm(self.selected) + + MDTimePickerButtonsContainer: + time_picker: root + icon: "clock-outline" + +############################################################################### +# +# DIAL RULES +# +############################################################################### + + + size_hint: None, None + size: dp(574), dp(380) + _time_input: _time_input + _selector: _selector + _am_pm_selector: _am_pm_selector + + MDLabel: + adaptive_size: True + text: root.headline_text + font_style: "Label" + role: "large" + padding: 0, 0, 0, dp(20) + + BoxLayout: + spacing: dp(52) + + BoxLayout: + orientation: "vertical" + spacing: dp(16) + padding: 0, 0, 0, dp(52) + + MDTimePickerInputContainer: + id: _time_input + time_picker: root + on_minute_select: _selector.switch_mode("minute") + on_hour_select: _selector.switch_mode("hour") + + MDTimePickerAmPmSelector: + id: _am_pm_selector + orientation: "horizontal" + size_hint: None, None + size: _time_input.width, dp(38) + on_selected: + root.dispatch("on_am_pm", self.selected) + root._get_am_pm(self.selected) + + MDTimePickerCircularSelector: + id: _selector + time_picker: root + + MDTimePickerButtonsContainer: + time_picker: root + + + + size_hint: None, None + size: dp(324), dp(520) + _time_input: _time_input + _selector: _selector + _am_pm_selector: _am_pm_selector + + MDLabel: + adaptive_size: True + text: root.headline_text + font_style: "Label" + role: "large" + padding: 0, 0, 0, dp(20) + + BoxLayout: + size_hint_y: None + height: self.minimum_height + spacing: dp(20) + + MDTimePickerInputContainer: + id: _time_input + time_picker: root + on_minute_select: _selector.switch_mode("minute") + on_hour_select: _selector.switch_mode("hour") + + MDTimePickerAmPmSelector: + id: _am_pm_selector + size_hint: None, None + size: dp(52), dp(80) + on_selected: + root.dispatch("on_am_pm", self.selected) + root._get_am_pm(self.selected) + + Widget: + size_hint_x: None + width: dp(20) + + MDTimePickerCircularSelector: + id: _selector + time_picker: root + + MDTimePickerButtonsContainer: + time_picker: root + +############################################################################### +# +# COMMON RULES +# +############################################################################### + + canvas.before: Color: - rgba: root.border_color + rgba: + self.theme_cls.surfaceContainerHighColor RoundedRectangle: + size: self.size + pos: self.pos + radius: self.radius + + orientation: "vertical" + elevation_level: 0 + shadow_color: self.theme_cls.transparentColor + pos_hint: {'center_x': .5, 'center_y': .5} + padding: dp(24) + opacity: 0 + + + + size_hint_y: None + height: self.minimum_height + padding: 0, dp(24), 0, 0 + + MDIconButton: + icon: root.icon + on_release: root.time_picker.dispatch("on_edit") + + Widget: + + MDButton: + style: "text" + on_release: root.time_picker.dispatch("on_cancel") + + MDButtonText: + text: + root.time_picker.text_button_cancel \ + if root.time_picker else \ + "" + + MDButton: + style: "text" + on_release: root.time_picker.dispatch("on_ok") + + MDButtonText: + text: + root.time_picker.text_button_ok \ + if root.time_picker else \ + "" + + + canvas: + Color: + rgba: self.color[:-1] + [self.alpha] + Rectangle: pos: self.pos size: self.size - radius: [root.border_radius, ] - #AM + + + canvas.after: Color: - rgba: root._am_bg_color - RoundedRectangle: - pos: + rgba: + self.theme_cls.primaryColor \ + if self.focus else \ + self.theme_cls.transparentColor + SmoothLine: + width: 1 + rounded_rectangle: [ \ - self.pos[0] + root.border_width, \ - self.pos[1] + self.height/2 + self.border_width * 0.5 \ - ] if self.orientation == "vertical" else \ - [ \ - self.pos[0] + root.border_width, \ - self.pos[1] + root.border_width \ + self.x, + self.y, \ + self.width, \ + self.height, \ + *self.radius, \ ] - size: - [ \ - self.size[0] - root.border_width * 2, \ - self.size[1] / 2 - self.border_width * 1.5 \ - ] if self.orientation == "vertical" else \ - [ \ - self.size[0] / 2 - root.border_width * 1.5, \ - self.size[1] - root.border_width * 2 \ - ] - radius: - [root.border_radius, root.border_radius, 0, 0] \ - if self.orientation == "vertical" else \ - [root.border_radius, 0, 0, root.border_radius] - #PM - Color: - rgba: root._pm_bg_color - RoundedRectangle: - pos: - [ \ - self.pos[0] + root.border_width, \ - self.pos[1] + self.border_width \ - ] if self.orientation == "vertical" else \ - [ \ - self.pos[0] + root.size[0] / 2 + root.border_width / 2, \ - self.pos[1] + root.border_width \ - ] - size: - [ \ - self.size[0] - root.border_width * 2, \ - self.size[1] / 2 - self.border_width * 1.5 \ - ] if self.orientation == "vertical" else \ - [ \ - self.size[0] / 2 - root.border_width * 1.5, \ - self.size[1] - root.border_width * 2 \ - ] - radius: - [0, 0, root.border_radius, root.border_radius] \ - if self.orientation == "vertical" else \ - [0 ,root.border_radius, root.border_radius, 0] - - # AM - AmPmSelectorLabel: - text: "AM" - on_release: root.selected = "am" - text_color: root.text_color - - AmPmSelectorLabel: - text: "PM" - on_release: root.selected = "pm" - text_color: root.text_color - - - - size_hint: None, 1 - width: dp(96) - mode: "fill" - active_line: False + mode: "filled" font_size: dp(56) - radius: [dp(10), ] - fill_color_normal: - root.parent.parent.parent.accent_color \ - if root.parent.parent.parent.accent_color else \ - ( \ - [*root.parent.bg_color_active[:3], 0.5] \ - if root.parent.state in ["hour", "minute"] else \ - [*root.bg_color[:3], 0.5] \ - ) - fill_color_focus: - (1, 1, 1, 0.5) \ - if root.parent.parent.parent.primary_color else \ - self.theme_cls.bg_dark - text_color_focus: - root.parent.parent.parent.accent_color \ - if root.parent.parent.parent.accent_color else \ - self.theme_cls.primary_color - - - + radius: [dp(12), ] size_hint: None, None + theme_bg_color: "Custom" + fill_color_focus: self.theme_cls.primaryContainerColor + fill_color_normal: self.theme_cls.surfaceContainerHighestColor + width: "96dp" + selection_color: self.theme_cls.transparentColor + -height: "80dp" + -padding: [dp(16 if self.text else 48), dp(6), dp(16), dp(6)] + + + + size_hint_y: None + height: "80dp" _hour: hour _minute: minute - TimeInputTextField: + MDTimePickerInputTextField: id: hour num_type: "hour" - pos: 0, 0 - text_color: root.text_color - disabled: root.disabled - on_text: root.dispatch("on_time_input") - radius: root.hour_radius + readonly: root._readonly + on_text: + root.time_picker._get_time_input(*root.get_time()) + root.time_picker.dispatch("on_time_input", "hour", self.text) on_select: root.dispatch("on_hour_select") root.state = "hour" @@ -137,29 +280,100 @@ size: dp(24), dp(80) halign: "center" valign: "center" + theme_font_size: "Custom" font_size: dp(50) - pos: dp(96), 0 - theme_text_color: "Custom" - text_color: root.text_color - TimeInputTextField: + MDTimePickerInputTextField: id: minute num_type: "minute" - pos: dp(120), 0 - text_color: root.text_color - disabled: root.disabled - on_text: root.dispatch("on_time_input") - radius: root.minute_radius + readonly: root._readonly + on_text: + root.time_picker._get_time_input(*root.get_time()) + root.time_picker.dispatch("on_time_input", "minute", self.text) on_select: root.dispatch("on_minute_select") root.state = "minute" - + + halign: "center" + valign: "center" + + + + orientation: "vertical" + line_color: self.theme_cls.outlineColor + radius: [dp(12), ] + + canvas.before: + # AM + Color: + rgba: + self.theme_cls.tertiaryContainerColor \ + if self.selected == "am" else \ + self.theme_cls.surfaceContainerHighColor + RoundedRectangle: + pos: + [self.pos[0], self.pos[1] + self.height / 2] \ + if self.orientation == "vertical" else \ + [self.pos[0], self.pos[1]] + size: + [self.size[0], self.size[1] / 2] \ + if self.orientation == "vertical" else \ + [self.size[0] / 2, self.size[1]] + radius: + [dp(12), dp(12), 0, 0] \ + if self.orientation == "vertical" else \ + [dp(12), 0, 0, dp(12)] + + # PM + Color: + rgba: + self.theme_cls.tertiaryContainerColor \ + if self.selected == "pm" else \ + self.theme_cls.surfaceContainerHighColor + RoundedRectangle: + pos: + [self.pos[0], self.pos[1]] \ + if self.orientation == "vertical" else \ + [self.pos[0] + root.size[0] / 2, self.pos[1]] + size: + [self.size[0], self.size[1] / 2] \ + if self.orientation == "vertical" else \ + [self.size[0] / 2, self.size[1]] + radius: + [0, 0, dp(12), dp(12)] \ + if self.orientation == "vertical" else \ + [0, dp(12), dp(12), 0] + + MDTimePickerAmPmSelectorLabel: + id: am_label + text: "AM" + on_release: root.selected = "am" + + MDDivider: + color: root.theme_cls.outlineColor + orientation: + "vertical" \ + if root.orientation == "horizontal" else \ + "horizontal" + + MDTimePickerAmPmSelectorLabel: + id: pm_label + text: "PM" + on_release: root.selected = "pm" + + + + adaptive_size: True + + + circular_padding: dp(28) size_hint: None, None - size: [dp(256), dp(256)] + size: dp(256), dp(256) row_spacing: dp(40) + on_selector_change: self.time_picker._get_dial_time(self) canvas.before: PushMatrix @@ -168,7 +382,7 @@ x: root.scale y: root.scale Color: - rgba: root.bg_color + rgba: self.theme_cls.surfaceContainerHighestColor Ellipse: size: self.size pos: self.pos @@ -178,7 +392,7 @@ x: root.content_scale y: root.content_scale Color: - rgb: root.selector_color + rgb: self.theme_cls.primaryColor a: 0 if self.selector_pos == [0, 0] else 1 Ellipse: size: self.selector_size, self.selector_size @@ -188,153 +402,9 @@ Ellipse: size: dp(10), dp(10) pos: [self.center[0] - dp(5), self.center[1] - dp(5)] - Line: + SmoothLine: points: [self.center, self.selector_pos] width: dp(1) canvas.after: PopMatrix PopMatrix - - - - halign: "center" - valign: "center" - adaptive_size: True - theme_text_color: "Custom" - - - - auto_dismiss: True - size_hint: None, None - _time_input: _time_input - _selector: _selector - _am_pm_selector: _am_pm_selector - _minute_label: _minute_label - _hour_label: _hour_label - - MDRelativeLayout: - canvas.before: - Color: - rgba: - root.primary_color \ - if root.primary_color \ - else root.theme_cls.bg_normal - - RoundedRectangle: - size: self.size - radius: root.radius - - MDLabel: - id: label_title - font_style: "Body2" - bold: True - theme_text_color: "Custom" - size_hint_x: None - width: root.width - adaptive_height: True - text: root.title - font_name: root.font_name - pos: (dp(24), root.height - self.height - dp(18)) - text_color: - root.text_toolbar_color if root.text_toolbar_color \ - else root.theme_cls.text_color - - TimeInput: - id: _time_input - bg_color: - root.accent_color if root.accent_color else \ - root.theme_cls.primary_light - bg_color_active: - root.selector_color if root.selector_color \ - else root.theme_cls.primary_color - text_color: - root.input_field_text_color if root.input_field_text_color else \ - root.theme_cls.text_color - on_time_input: root._get_time_input(*self.get_time()) - on_hour_select: _selector.switch_mode("hour") - on_minute_select: _selector.switch_mode("minute") - minute_radius: root.minute_radius - hour_radius: root.hour_radius - - TimeInputLabel: - id: _hour_label - text: "Hour" - opacity: 0 - text_color: - root.text_toolbar_color if root.text_toolbar_color else \ - root.theme_cls.secondary_text_color - - TimeInputLabel: - id: _minute_label - text: "Minute" - opacity: 0 - text_color: - root.text_toolbar_color if root.text_toolbar_color else \ - root.theme_cls.secondary_text_color - - AmPmSelector: - id: _am_pm_selector - owner: root - border_color: - root.accent_color if root.accent_color else \ - root.theme_cls.primary_color - border_radius: root.am_pm_radius - bg_color: - root.primary_color if root.primary_color else \ - root.theme_cls.bg_normal - border_width: root.am_pm_border_width - bg_color_active: - root.selector_color if root.selector_color else \ - root.theme_cls.primary_light - text_color: - root.input_field_text_color if root.input_field_text_color else \ - root.theme_cls.text_color - on_selected: root._get_am_pm(self.selected) - - CircularSelector: - id: _selector - text_color: - root.text_color if root.text_color else \ - root.theme_cls.text_color - bg_color: - root.accent_color if root.accent_color else \ - root.theme_cls.primary_light - selector_color: - root.primary_color if root.primary_color else \ - root.theme_cls.primary_color - font_name: root.font_name - on_selector_change: root._get_dial_time(_selector) - - MDIconButton: - id: input_clock_switch - icon: "keyboard" - pos: dp(12), dp(8) - theme_icon_color: "Custom" - icon_size: "24dp" - on_release: root._switch_input() - icon_color: - root.text_toolbar_color if root.text_toolbar_color else \ - root.theme_cls.secondary_text_color - - MDFlatButton: - id: cancel_button - text: "CANCEL" - on_release: root.dispatch("on_cancel", None) - theme_text_color: "Custom" - pos: root.width - self.width - ok_button.width - dp(10), dp(10) - font_name: root.font_name - text_color: - root.theme_cls.primary_color \ - if not root.text_button_color else root.text_button_color - - MDFlatButton: - id: ok_button - width: dp(32) - pos: root.width - self.width, dp(10) - text: "OK" - theme_text_color: "Custom" - font_name: root.font_name - text_color: - root.theme_cls.primary_color \ - if not root.text_button_color else root.text_button_color - on_release: root.dispatch("on_save", root._get_data()) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.py index e45a784..ebb1cfb 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/pickers/timepicker/timepicker.py @@ -4,152 +4,507 @@ Components/TimePicker .. seealso:: - `Material Design spec, Time picker `_ + `Material Design spec, Date picker `_ -.. rubric:: Includes time picker. +.. rubric:: Time pickers help users select and set a specific time. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/picker-previous.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker.png :align: center -.. warning:: The widget is under testing. Therefore, we would be grateful if - you would let us know about the bugs found. +- Time pickers are modal and cover the main content +- Two types: dial and input +- Users can select hours, minutes, or periods of time +- Make sure time can easily be selected by hand on a mobile device -.. rubric:: Usage - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.pickers import MDTimePicker - - KV = ''' - MDFloatLayout: - - MDRaisedButton: - text: "Open time picker" - pos_hint: {'center_x': .5, 'center_y': .5} - on_release: app.show_time_picker() - ''' - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def show_time_picker(self): - '''Open time picker dialog.''' - - time_dialog = MDTimePicker() - time_dialog.open() - - - Test().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.button import MDRaisedButton - from kivymd.uix.pickers import MDTimePicker - from kivymd.uix.screen import MDScreen - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDRaisedButton( - text="Open time picker", - pos_hint={'center_x': .5, 'center_y': .5}, - on_release=self.show_time_picker, - ) - ) - ) - - def show_time_picker(self, *args): - '''Open time picker dialog.''' - - MDTimePicker().open() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDTimePicker.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-types.png :align: center -Binding method returning set time ---------------------------------- +1. Vertical dial time picker +2. Horizontal dial time picker +3. Time picker input + +KivyMD provides the following date pickers classes for use: + +- MDTimePickerDialVertical_ +- MDTimePickerDialHorizontal_ +- MDTimePickerInput_ + +.. _MDTimePickerDialVertical: + +MDTimePickerDialVertical +------------------------ + +Time pickers allow people to enter a specific time value. They’re displayed in +dialogs and can be used to select hours, minutes, or periods of time. + +They can be used for a wide range of scenarios. Common use cases include: + +- Setting an alarm +- Scheduling a meeting + +Time pickers are not ideal for nuanced or granular time selection, such as +milliseconds for a stopwatch application. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/vertical-time-picker-preview.gif + :align: center + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDTimePickerDialVertical + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_time_picker() + + MDButtonText: + text: "Open time picker" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + def show_time_picker(self): + time_picker = MDTimePickerDialVertical() + time_picker.open() + + + Example().run() + +.. _MDTimePickerDialHorizontal: + +MDTimePickerDialHorizontal +-------------------------- + +The clock dial interface adapts to a device’s orientation. In landscape mode, +the stacked input and selection options are positioned side-by-side. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/horizontal-time-picker-preview.gif + :align: center .. code-block:: python def show_time_picker(self): - time_dialog = MDTimePicker() - time_dialog.bind(time=self.get_time) - time_dialog.open() + MDTimePickerDialHorizontal().open() - def get_time(self, instance, time): - ''' - The method returns the set time. +.. note:: You must control the orientation of the time picker yourself. - :type instance: - :type time: - ''' +.. code-block:: python - return time + from typing import Literal -Open time dialog with the specified time ----------------------------------------- + from kivy.clock import Clock + from kivy.lang import Builder + from kivy.properties import ObjectProperty -Use the :attr:`~MDTimePicker.set_time` method of the -:class:`~MDTimePicker.` class. + from kivymd.app import MDApp + from kivymd.theming import ThemeManager + from kivymd.uix.pickers import ( + MDTimePickerDialHorizontal, + MDTimePickerDialVertical, + ) + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: + app.open_time_picker_horizontal("1", "10") \ + if self.theme_cls.device_orientation == "landscape" else \ + app.open_time_picker_vertical("1", "10") + + MDButtonText: + text: "Open time picker" + ''' + + + class Example(MDApp): + ORIENTATION = Literal["portrait", "landscape"] + time_picker_horizontal: MDTimePickerDialHorizontal = ObjectProperty( + allownone=True + ) + time_picker_vertical: MDTimePickerDialHorizontal = ObjectProperty( + allownone=True + ) + + def build(self): + self.theme_cls.theme_style = "Dark" + self.theme_cls.bind(device_orientation=self.check_orientation) + return Builder.load_string(KV) + + def check_orientation( + self, instance: ThemeManager, orientation: ORIENTATION + ): + if orientation == "portrait" and self.time_picker_horizontal: + self.time_picker_horizontal.dismiss() + hour = str(self.time_picker_horizontal.time.hour) + minute = str(self.time_picker_horizontal.time.minute) + Clock.schedule_once( + lambda x: self.open_time_picker_vertical(hour, minute), + 0.1, + ) + elif orientation == "landscape" and self.time_picker_vertical: + self.time_picker_vertical.dismiss() + hour = str(self.time_picker_vertical.time.hour) + minute = str(self.time_picker_vertical.time.minute) + Clock.schedule_once( + lambda x: self.open_time_picker_horizontal(hour, minute), + 0.1, + ) + + def open_time_picker_horizontal(self, hour, minute): + self.time_picker_vertical = None + self.time_picker_horizontal = MDTimePickerDialHorizontal( + hour=hour, minute=minute + ) + self.time_picker_horizontal.open() + + def open_time_picker_vertical(self, hour, minute): + self.time_picker_horizontal = None + self.time_picker_vertical = MDTimePickerDialVertical( + hour=hour, minute=minute + ) + self.time_picker_vertical.open() + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-control-orientation.gif + :align: center + +.. _MDTimePickerInput: + +MDTimePickerInput +----------------- + +Time input pickers allow people to specify a time using keyboard numbers. +This input option should be accessible from any other mobile time picker +interface by tapping the keyboard icon. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/input-time-picker-preview.gif + :align: center .. code-block:: python def show_time_picker(self): - from datetime import datetime + MDTimePickerInput().open() - # Must be a datetime object - previous_time = datetime.strptime("03:20:00", '%H:%M:%S').time() - time_dialog = MDTimePicker() - time_dialog.set_time(previous_time) - time_dialog.open() +Events +====== -.. note:: For customization of the :class:`~MDTimePicker` class, see the - documentation in the :class:`~kivymd.uix.pickers.datepicker.datepicker.BaseDialogPicker` class. +**on_edit** event +----------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-edit.gif + :align: center .. code-block:: python - MDTimePicker( - primary_color="brown", - accent_color="red", - text_button_color="white", + from kivy.clock import Clock + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.pickers import MDTimePickerDialVertical, MDTimePickerInput + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDButton: + pos_hint: {'center_x': .5, 'center_y': .5} + on_release: app.show_time_picker_vertical() + + MDButtonText: + text: "Open time picker" + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + def on_edit_time_picker_input(self, time_picker_input): + time_picker_input.dismiss() + Clock.schedule_once(self.show_time_picker_vertical, 0.2) + + def show_time_picker_input(self, *args): + time_picker_input = MDTimePickerInput() + time_picker_input.bind(on_edit=self.on_edit_time_picker_input) + time_picker_input.open() + + def on_edit_time_picker_vertical(self, time_picker_vertical): + time_picker_vertical.dismiss() + Clock.schedule_once(self.show_time_picker_input, 0.2) + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_edit=self.on_edit_time_picker_vertical) + time_picker_vertical.open() + + + Example().run() + +**on_hour_select** event +------------------------ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-hour-select.gif + :align: center + +.. code-block:: python + + def on_hour_select( + self, time_picker_vertical: MDTimePickerDialVertical, mode: str + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"On '{mode}' select", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, ).open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-customization.png + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_hour_select=self.on_hour_select) + time_picker_vertical.open() + +**on_minute_select** event +-------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-minute-select.gif :align: center + +.. code-block:: python + + def on_minute_select( + self, time_picker_vertical: MDTimePickerDialVertical, mode: str + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"On '{mode}' select", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_minute_select=self.on_minute_select) + time_picker_vertical.open() + +**on_am_pm** event +------------------ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-am-pm.gif + :align: center + +.. code-block:: python + + def on_am_pm( + self, time_picker_vertical: MDTimePickerDialVertical, am_pm: str + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"'{am_pm.upper()}' select", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_am_pm=self.on_am_pm) + time_picker_vertical.open() + +**on_selector_hour** event +-------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-selector-hour.gif + :align: center + +.. code-block:: python + + def on_selector_hour( + self, time_picker_vertical: MDTimePickerDialVertical, hour: str + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"The value of the hour is `{hour}` select", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_selector_hour=self.on_selector_hour) + time_picker_vertical.open() + +**on_selector_minute** event +---------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-selector-minute.gif + :align: center + +.. code-block:: python + + def on_selector_minute( + self, time_picker_vertical: MDTimePickerDialVertical, minute: str + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"The value of the hour is `{minute}` select", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_selector_minute=self.on_selector_minute) + time_picker_vertical.open() + +**on_cancel** event +------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-cancel.gif + :align: center + +.. code-block:: python + + def on_cancel( + self, time_picker_vertical: MDTimePickerDialVertical + ): + time_picker_vertical.dismiss() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_cancel=self.on_cancel) + time_picker_vertical.open() + +**on_ok** event +--------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-vertical-event-on-ok.gif + :align: center + +.. code-block:: python + + def on_ok( + self, time_picker_vertical: MDTimePickerDialVertical + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"Time is `{time_picker_vertical.time}`", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerDialVertical() + time_picker_vertical.bind(on_ok=self.on_ok) + time_picker_vertical.open() + +**on_time_input** event +----------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-input-event-on-time-input.gif + :align: center + +.. code-block:: python + + def on_time_input( + self, + time_picker_vertical: MDTimePickerInput, + type_time: str, + value: str, + ): + MDSnackbar( + MDSnackbarSupportingText( + text=f"The {type_time} value is set to {value}", + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + + def show_time_picker_vertical(self, *args): + time_picker_vertical = MDTimePickerInput() + time_picker_vertical.bind(on_time_input=self.on_time_input) + time_picker_vertical.open() + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + time_picker_dialog = MDTimePicker() + time_picker_dialog.open() + +2.0.0 version +------------- + +.. code-block:: python + + # time_picker_dialog = MDTimePickerDialVertical() + # time_picker_dialog = MDTimePickerDialHorizontal() + + time_picker_dialog = MDTimePickerInput() + time_picker_dialog.open() """ -__all__ = ("MDTimePicker",) +# TODO: Implement 24h input. +# Implement a tooltip for time picker elements. +# Should we implement the feature to settings custom colors for the +# time picker dialogs? + +from __future__ import annotations + +__all__ = ( + "MDBaseTimePicker", + "MDTimePickerInput", + "MDTimePickerDialVertical", + "MDTimePickerDialHorizontal", +) import datetime import os import re import time -from typing import List, Union +from typing import List from kivy.animation import Animation from kivy.clock import Clock -from kivy.event import EventDispatcher +from kivy.core.window import Window from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( @@ -163,14 +518,19 @@ from kivy.properties import ( VariableListProperty, ) from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.widget import Widget from kivy.vector import Vector from kivymd import uix_path -from kivymd.uix.boxlayout import MDBoxLayout +from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import ( + CommonElevationBehavior, + BackgroundColorBehavior, +) +from kivymd.uix.behaviors.motion_behavior import MotionTimePickerBehavior from kivymd.uix.circularlayout import MDCircularLayout from kivymd.uix.label import MDLabel -from kivymd.uix.pickers.datepicker import BaseDialogPicker -from kivymd.uix.relativelayout import MDRelativeLayout from kivymd.uix.textfield import MDTextField with open( @@ -180,175 +540,387 @@ with open( Builder.load_string(kv_file.read()) -class AmPmSelectorLabel(ButtonBehavior, MDLabel): - pass +class MDBaseTimePicker(ThemableBehavior, MotionTimePickerBehavior, BoxLayout): + """ + Implements the base class of the time picker. + .. versionadded:: 2.0.0 -class AmPmSelector(MDBoxLayout): - border_radius = NumericProperty() - border_color = ColorProperty() - bg_color = ColorProperty() - bg_color_active = ColorProperty() - border_width = NumericProperty() - am = ObjectProperty() - am = ObjectProperty() - owner = ObjectProperty() - text_color = ColorProperty() - selected = StringProperty() + For more information, see in the + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.motion_behavior.MotionTimePickerBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` and + classes documentation. - _am_bg_color = ColorProperty() - _pm_bg_color = ColorProperty() + :Events: + :attr:`on_cancel` + Fired when the 'Cancel' button is pressed. + :attr:`on_ok` + Fired when the 'Ok' button is pressed. + :attr:`on_dismiss` + Fired when a date picker closes. + :attr:`on_edit` + Fired when you click on the date editing icon. + :attr:`on_hour_select` + Fired when the hour input field container is clicked. + :attr:`on_minute_select` + Fired when the minute input field container is clicked. + :attr:`on_am_pm` + Fired when the AP/PM switching elements are pressed. + :attr:`on_selector_hour` + Fired when switching the hour value in the clock face container. + :attr:`on_selector_minute` + Fired when switching the minute value in the clock face container. + """ + + hour = StringProperty("12") + """ + Current hour. + + :attr:`hour` is an :class:`~kivy.properties.StringProperty` + and defaults to `'12'`. + """ + + minute = StringProperty("0") + """ + Current minute. + + :attr:`minute` is an :class:`~kivy.properties.StringProperty` + and defaults to `0`. + """ + + am_pm = OptionProperty("am", options=["am", "pm"]) + """ + Current AM/PM mode. + + :attr:`am_pm` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'am'`. + """ + + animation_duration = NumericProperty(0.3) + """ + Duration of the animations. + + :attr:`animation_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ + + animation_transition = StringProperty("out_quad") + """ + Transition type of the animations. + + :attr:`animation_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_quad'`. + """ + + time = ObjectProperty(allownone=True) + """ + Returns the current time object. + + :attr:`time` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + headline_text = StringProperty("Select time") + """ + Headline text. + + :attr:`headline_text` is an :class:`~kivy.properties.StringProperty` + and defaults to `'Select time'`. + """ + + text_button_ok = StringProperty("Ok") + """ + The text of the confirmation button. + + :attr:`text_button_ok` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Ok'`. + """ + + text_button_cancel = StringProperty("Cancel") + """ + The text of the cancel button. + + :attr:`text_button_cancel` is a :class:`~kivy.properties.StringProperty` + and defaults to `'Cancel'`. + """ + + radius = VariableListProperty([dp(16)], length=4) + """ + Container radius. + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(16), dp(16), dp(16), dp(16)]`. + """ + + is_open = BooleanProperty(False) + """ + Is the date picker dialog open. + + :attr:`is_open` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + scrim_color = ColorProperty([0, 0, 0, 0.5]) + """ + Color for scrim in (r, g, b, a) or string format. + + :attr:`scrim_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `[0, 0, 0, 0.5]`. + """ + + _selector = ObjectProperty() + _time_input = ObjectProperty() + _am_pm_selector = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) - self.bind(selected=self._upadte_color) - Clock.schedule_once(self._upadte_color) - - def _upadte_color(self, *args): - bg_color = ( - self.owner.accent_color - if self.owner.accent_color - else self.bg_color_active + self.bind( + hour=self._set_current_time, + minute=self._set_current_time, + am_pm=self._set_current_time, ) - if self.selected == "am": - self._am_bg_color = bg_color - self._pm_bg_color = ( - self.owner.primary_color - if self.owner.accent_color - else self.bg_color - ) - elif self.selected == "pm": - self._am_bg_color = ( - self.owner.primary_color - if self.owner.accent_color - else self.bg_color - ) - self._pm_bg_color = bg_color - - -class TimeInputTextField(MDTextField): - num_type = OptionProperty("hour", options=["hour", "minute"]) - hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$" - minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - Clock.schedule_once(self.set_text) - self.register_event_type("on_select") - self.bind(text_color_focus=self.setter("hint_text_color_normal")) - - def validate_time(self, text) -> Union[None, re.Match]: - reg = self.hour_regx if self.num_type == "hour" else self.minute_regx - return re.match(reg, text) - - def insert_text(self, text, from_undo=False): - strip_text = self.text.strip() - current_string = "".join([strip_text, text]) - if not self.validate_time(current_string): - text = "" - return super().insert_text(text, from_undo=from_undo) - - def set_text(self, *args) -> None: - """ - Texts should be center aligned. Now we are setting the padding of text - to somehow make them aligned. - """ - - def set_text(*args): - if not self.text: - self.text = " " - - self._refresh_text(self.text) - max_size = max(self._lines_rects, key=lambda r: r.size[0]).size - dx = (self.width - max_size[0]) / 2.0 - dy = (self.height - max_size[1]) / 2.0 - self.padding = [dx, dy, dx, dy] - - if len(self.text) > 1: - self.text = self.text.replace(" ", "") - - Clock.schedule_once(set_text) - - def on_focus(self, *args) -> None: - super().on_focus(*args) - if self.text.strip(): - if ( - not self.focus - and int(self.text) == 0 - and self.num_type == "hour" - ): - self.text = "12" - else: - self.text = " 12" if self.num_type == "hour" else " 00" - - def on_select(self, *args) -> None: - pass - - def on_touch_down(self, touch): - if self.collide_point(*touch.pos): - self.dispatch("on_select") - super().on_touch_down(touch) - - -class TimeInput(MDRelativeLayout): - """Implements two text fields for displaying and entering a time value.""" - - bg_color = ColorProperty() - bg_color_active = ColorProperty() - text_color = ColorProperty() - disabled = BooleanProperty(True) - minute_radius = ListProperty([0, 0, 0, 0]) - hour_radius = ListProperty([0, 0, 0, 0]) - state = StringProperty("hour") - - _hour = ObjectProperty() - _minute = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_time_input") + self.register_event_type("on_dismiss") + self.register_event_type("on_cancel") + self.register_event_type("on_ok") + self.register_event_type("on_edit") self.register_event_type("on_hour_select") self.register_event_type("on_minute_select") + self.register_event_type("on_am_pm") + self.register_event_type("on_selector_hour") + self.register_event_type("on_selector_minute") + self.register_event_type("on_time_input") + Clock.schedule_once( + lambda x: self.set_time( + datetime.time(hour=int(self.hour), minute=int(self.minute)) + ) + ) # default time - def set_time(self, time_list) -> None: - hour, minute = time_list - self._hour.text = hour - self._minute.text = minute + def set_time(self, time_obj: datetime.time) -> None: + """Manually set time dialog with the specified time.""" - def get_time(self) -> List[str]: - hour = self._hour.text.strip() - minute = self._minute.text.strip() - return [hour, minute] + hour = time_obj.hour + minute = time_obj.minute - def on_time_input(self, *args) -> None: - pass + if hour > 12: + hour -= 12 + mode = "pm" + else: + mode = "am" - def on_minute_select(self, *args) -> None: - pass + hour = str(hour) + minute = str(minute) + self._set_time_input(hour, minute) + self._set_dial_time(hour, minute) + self._set_am_pm(mode) + self._set_current_time() + + def open(self) -> None: + """Show the dialog time picker.""" + + if not self.is_open: + if not self._scrim: + self._scrim = MDTimePickerScrim(color=self.scrim_color) + + Window.add_widget(self._scrim) + Window.add_widget(self) + super().on_open() + self.is_open = True + + def _get_dial_time(self, instance): + mode = instance.mode + if mode == "hour": + self.hour = instance.selected_hour + self.dispatch("on_selector_hour", self.hour) + elif mode == "minute": + self.minute = instance.selected_minute + self.dispatch("on_selector_minute", self.minute) + else: + raise Exception("invalid mode for MDTimePicker: " % mode) + + self._set_time_input(self.hour, self.minute) + + def _set_dial_time(self, hour, minute): + if self._selector: + self._selector.selected_minute = minute + self._selector.selected_hour = hour + else: + self.hour, self.minute = hour, minute + self._set_current_time() + + def _get_time_input(self, hour, minute): + if hour: + self.hour = f"{int(hour):01d}" + self.dispatch("on_time_input", "hour", self.hour) + if minute: + self.minute = f"{int(minute):01d}" + self.dispatch("on_time_input", "minute", self.minute) + + self._set_dial_time(self.hour, self.minute) + + def _set_time_input(self, hour, minute): + hour = f"{int(hour):02d}" + minute = f"{int(minute):02d}" + + if self._time_input: + self._time_input.set_time([hour, minute]) + + def _get_am_pm(self, selected): + self.am_pm = selected + + def _set_am_pm(self, selected: str) -> None: + """Used by set_time() to manually set the mode to "am" or "pm".""" + + self.am_pm = selected + self._am_pm_selector.mode = self.am_pm + self._am_pm_selector.selected = self.am_pm + + def on_touch_down(self, touch): + if not self.collide_point(*touch.pos): + self.dismiss() + return True + super().on_touch_down(touch) + return True + + def dismiss(self, *args) -> None: + """Dismiss the dialog time picker.""" + + super().on_dismiss() + self.is_open = False + + def on_dismiss(self, *args) -> None: + """Fired when a time picker closes.""" + + def on_cancel(self, *args) -> None: + """Fired when the 'Cancel' button is pressed.""" + + def on_ok(self, *args) -> None: + """Fired when the 'Ok' button is pressed.""" def on_hour_select(self, *args) -> None: - pass + """Fired when the hour input field container is clicked.""" - def _update_padding(self, *args): - self._hour.set_text() - self._minute.set_text() + def on_minute_select(self, *args) -> None: + """Fired when the minute input field container is clicked.""" + + def on_am_pm(self, *args) -> None: + """Fired when the AP/PM switching elements are pressed.""" + + def on_edit(self, *args) -> None: + """Fired when you click on the time editing icon.""" + + def on_selector_hour(self, *args) -> None: + """Fired when switching the hour value in the clock face container.""" + + def on_selector_minute(self, *args) -> None: + """ + Fired when switching the minute value in the clock face container. + """ + + def on_time_input(self, *args) -> None: + """ + Fired when switching the minute value in the clock face container. + """ + + def _get_data(self): + try: + if time.strftime("%p"): + result = datetime.datetime.strptime( + f"{int(self.hour):02d}:{int(self.minute):02d} {self.am_pm}", + "%I:%M %p", + ).time() + else: + result = datetime.datetime.strptime( + f"{int(self.hour):02d}:{int(self.minute):02d}", + "%I:%M", + ).time() + return result + except ValueError: + return None # hour is zero + + def _set_current_time(self, *args): + self.time = self._get_data() -class SelectorLabel(MDLabel): - pass +############################################################################### +# +# PICKER CLASSES +# +############################################################################### -class CircularSelector(MDCircularLayout, EventDispatcher): - """Implements clock face display.""" +class MDTimePickerInput(CommonElevationBehavior, MDBaseTimePicker): + """ + Implements input time picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~MDBaseTimePicker` + classes documentation. + """ + + +class MDTimePickerDialVertical(CommonElevationBehavior, MDBaseTimePicker): + """ + Implements vertical time picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~MDBaseTimePicker` + classes documentation. + """ + + +class MDTimePickerDialHorizontal(CommonElevationBehavior, MDBaseTimePicker): + """ + Implements horizontal time picker. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~MDBaseTimePicker` + classes documentation. + """ + + +############################################################################### +# +# COMMON CLASSES +# +############################################################################### + + +class MDTimePickerCircularSelectorLabel(MDLabel): + """ + Implements a label for the :class:`~MDTimePickerCircularSelector` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ + + +class MDTimePickerCircularSelector(ThemableBehavior, MDCircularLayout): + """ + Implements clock face display. + + For more information, see in the + :class:`~kivymd.uix.behaviors.ThemableBehavior` and + :class:`~kivymd.uix.circularlayout.MDCircularLayout` + classes documentation. + """ mode = OptionProperty("hour", options=["hour", "minute"]) # and military - text_color = ColorProperty() selected_hour = StringProperty("12") selected_minute = StringProperty("0") selector_size = NumericProperty("48dp") selector_pos = ListProperty([0, 0]) - selector_color = ColorProperty() - bg_color = ColorProperty() font_name = StringProperty() scale = NumericProperty(1) content_scale = NumericProperty(1) @@ -356,6 +928,14 @@ class CircularSelector(MDCircularLayout, EventDispatcher): d = NumericProperty(0.2) scale_origin = ListProperty([100, 100]) + time_picker: MDBaseTimePicker = ObjectProperty() + """ + Time picker object - + + :attr:`time_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + _centers_pos = ListProperty() def __init__(self, **kwargs): @@ -377,13 +957,13 @@ class CircularSelector(MDCircularLayout, EventDispatcher): widget = None for wid in self.children: - wid.text_color = self.text_color + wid.text_color = self.theme_cls.onSurfaceColor if wid.text == selected: widget = wid if not widget: return False self.selector_pos = widget.center - widget.text_color = [1, 1, 1, 1] + widget.text_color = self.theme_cls.onPrimaryColor self.dispatch("on_selector_change") return True @@ -406,6 +986,11 @@ class CircularSelector(MDCircularLayout, EventDispatcher): if mode != self.mode: self.mode = mode + if self.mode == "hour": + self.time_picker.dispatch("on_hour_select", self.mode) + elif self.mode == "minute": + self.time_picker.dispatch("on_minute_select", self.mode) + def on_touch_down(self, touch): if self.collide_point(*touch.pos): touch.grab(self) @@ -424,7 +1009,7 @@ class CircularSelector(MDCircularLayout, EventDispatcher): return True def on_selector_change(self, *args): - pass + ... def _update_labels(self, animate=True, *args): """ @@ -462,20 +1047,19 @@ class CircularSelector(MDCircularLayout, EventDispatcher): self.clear_widgets() i = 0 + for x in range(start, end + 1): - label = SelectorLabel( + label = MDTimePickerCircularSelectorLabel( text=f"{x}", ) if i % step != 0: label.opacity = 0 - self.bind( - text_color=label.setter("text_color"), - font_name=label.setter("font_name"), - ) self.add_widget(label) i += 1 + Clock.schedule_once(self.update_time) Clock.schedule_once(self._get_centers, 0.1) + anim = Animation(content_scale=1, t=self.t, d=self.d) anim.start(self) @@ -486,6 +1070,7 @@ class CircularSelector(MDCircularLayout, EventDispatcher): """ self._centers_pos = [] + for child in self.children: self._centers_pos.append(child.center) @@ -496,337 +1081,232 @@ class CircularSelector(MDCircularLayout, EventDispatcher): """ distance = [Vector(pos).distance(point) for point in self._centers_pos] + if not distance: return False + index = distance.index(min(distance)) + return self.children[index] -class MDTimePicker(BaseDialogPicker): - hour = StringProperty("12") +class MDTimePickerScrim(Widget): """ - Current hour. + Implements scrim for the time picker. - :attr:`hour` is an :class:`~kivy.properties.StringProperty` - and defaults to `'12'`. + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.widget.Widget` and class documentation. """ - minute = StringProperty("0") - """ - Current minute. + color = ColorProperty(None) + alpha = NumericProperty(0) - :attr:`minute` is an :class:`~kivy.properties.StringProperty` - and defaults to `0`. + +class MDTimePickerButtonsContainer(BoxLayout): + """ + Implements a container with buttons for time picker dialogs. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. """ - minute_radius = VariableListProperty(dp(5), length=4) + icon = StringProperty("keyboard") """ - Radius of the minute input field. + The leading container icon. - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-minute-radius.png - :align: center - - :attr:`minute_radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[dp(5), dp(5), dp(5), dp(5)]`. + :attr:`icon` is an :class:`~kivy.properties.StringProperty` + and defaults to `'keyboard'`. """ - hour_radius = VariableListProperty(dp(5), length=4) + time_picker: MDBaseTimePicker = ObjectProperty() """ - Radius of the hour input field. + Time picker object - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-hour-radius.png - :align: center - - :attr:`hour_radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[dp(5), dp(5), dp(5), dp(5)]`. - """ - - am_pm_radius = NumericProperty("5dp") - """ - Radius of the AM/PM selector. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-am-pm-radius.png - :align: center - - :attr:`am_pm_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(5)`. - """ - - am_pm_border_width = NumericProperty("1dp") - """ - Width of the AM/PM selector's borders. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/time-picker-am-pm-border-width.png - :align: center - - :attr:`am_pm_border_width` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(1)`. - """ - - am_pm = OptionProperty("am", options=["am", "pm"]) - """ - Current AM/PM mode. - - :attr:`am_pm` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'am'`. - """ - - animation_duration = NumericProperty(0.3) - """ - Duration of the animations. - - :attr:`animation_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - animation_transition = StringProperty("out_quad") - """ - Transition type of the animations. - - :attr:`animation_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'out_quad'`. - """ - - time = ObjectProperty(allownone=True) - """ - Returns the current time object. - - :attr:`time` is an :class:`~kivy.properties.ObjectProperty` + :attr:`time_picker` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ - _state = StringProperty() - _selector = ObjectProperty() - _time_input = ObjectProperty() - _am_pm_selector = ObjectProperty() - _hour_label = ObjectProperty() - _minute_label = ObjectProperty() - _anim_playing = BooleanProperty(False) + +class MDTimePickerAmPmSelectorLabel(ButtonBehavior, MDLabel): + """ + Implements a label for the :class:`~MDTimePickerAmPmSelector` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.uix.label.label.MDLabel` + classes documentation. + """ + + +class MDTimePickerAmPmSelector( + ThemableBehavior, BackgroundColorBehavior, BoxLayout +): + """ + Implements a container for AM/PM switching elements. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + """ + + selected = StringProperty() + """ + Time type status - AM/PM. + Real value - 'am/pm' + + :attr:`selected` is an :class:`~kivy.properties.StringProperty` + and defaults to `''`. + """ + + +class MDTimePickerInputContainer(BoxLayout): + """ + Implements a container for the hours and minutes text input fields. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + time_picker: MDBaseTimePicker = ObjectProperty() + """ + Time picker object - + + :attr:`time_picker` is an :class:`~kivy.properties.ObjectProperty` + and defaults to `None`. + """ + + state = OptionProperty("hour", options=["hour", "minute"]) + """ + Container status: entering hours or minutes. + Available options are: 'hour', 'minute'. + + :attr:`state` is an :class:`~kivy.properties.StringProperty` + and defaults to `'hour'`. + """ + + # State of the text fields for entering hours/minutes. + _readonly = BooleanProperty(True) + # MDTimePickerInputTextField objects. + _hour = ObjectProperty() + _minute = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) - self.bind( - hour=self._set_current_time, - minute=self._set_current_time, - am_pm=self._set_current_time, - ) - self.theme_cls.bind(device_orientation=self._check_orienation) - if self.title == "SELECT DATE": - self.title = "SELECT TIME" - self.set_time(datetime.time(hour=12, minute=0)) # default time - self._check_orienation() + self.register_event_type("on_time_input") + self.register_event_type("on_hour_select") + self.register_event_type("on_minute_select") - def set_time(self, time_obj) -> None: - """Manually set time dialog with the specified time.""" + def set_time(self, time_list) -> None: + hour, minute = time_list + self._hour.text = hour + self._minute.text = minute - hour = time_obj.hour - minute = time_obj.minute - if hour > 12: - hour -= 12 - mode = "pm" - else: - mode = "am" - hour = str(hour) - minute = str(minute) - self._set_time_input(hour, minute) - self._set_dial_time(hour, minute) - self._set_am_pm(mode) + def get_time(self) -> List[str]: + hour = self._hour.text.strip() + minute = self._minute.text.strip() + return [hour, minute] - def get_state(self) -> str: + def on_time_input(self, *args) -> None: + ... + + def on_minute_select(self, *args) -> None: + pass + + def on_hour_select(self, *args) -> None: + pass + + +class MDTimePickerInputTextField(MDTextField): + """ + Implements a text field for entering hour and minute values. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.textfield.textfield.MDTextField` class documentation. + """ + + num_type = OptionProperty("hour", options=["hour", "minute"]) + hour_regx = "^[0-9]$|^0[1-9]$|^1[0-2]$" + minute_regx = "^[0-9]$|^0[0-9]$|^[1-5][0-9]$" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_event_type("on_select") + Clock.schedule_once(self._override_properties, 0) + + def validate_time(self, text) -> None | re.Match: + reg = self.hour_regx if self.num_type == "hour" else self.minute_regx + return re.match(reg, text) + + def insert_text(self, text, from_undo=False): """ - Returns the current state of TimePicker. - Can be one of `portrait`, `landscape` or `input`. + Insert new text at the current cursor position. Override this + function in order to pre-process text for input validation. """ - return self._state + strip_text = self.text.strip() + current_string = "".join([strip_text, text]) + if not self.validate_time(current_string): + text = "" + return super().insert_text(text, from_undo=from_undo) - def _get_dial_time(self, instance): - mode = instance.mode - if mode == "hour": - self.hour = instance.selected_hour - elif mode == "minute": - self.minute = instance.selected_minute + def set_text(self, instance, text: str) -> None: + """ + Just override the method since its work in this class is not needed. + """ + + def on_focus(self, *args) -> None: + """Fired when the `focus` value changes.""" + + super().on_focus(*args) + Clock.schedule_once(self._override_properties, -1) + + if self.text.strip(): + if ( + not self.focus + and int(self.text) == 0 + and self.num_type == "hour" + ): + self.text = "12" else: - raise Exception("invalid mode for MDTimePicker: " % mode) - self._set_time_input(self.hour, self.minute) + self.text = "12" if self.num_type == "hour" else "00" - def _set_dial_time(self, hour, minute): - self._selector.selected_minute = minute - self._selector.selected_hour = hour + def on_select(self, *args) -> None: + """Fired when the hour/minute input field container is clicked.""" - def _get_time_input(self, hour, minute): - if hour: - self.hour = f"{int(hour):01d}" - if minute: - self.minute = f"{int(minute):01d}" - self._set_dial_time(self.hour, self.minute) + def on_touch_down(self, touch): + if self.collide_point(*touch.pos): + self.dispatch("on_select") + return super().on_touch_down(touch) - def _set_time_input(self, hour, minute): - hour = f"{int(hour):02d}" - minute = f"{int(minute):02d}" - if self._state != "input": - self._time_input.set_time([hour, minute]) - - def _get_am_pm(self, selected): - self.am_pm = selected - - def _set_am_pm(self, selected: str) -> None: - """Used by set_time() to manually set the mode to "am" or "pm".""" - self.am_pm = selected - self._am_pm_selector.mode = self.am_pm - self._am_pm_selector.selected = self.am_pm - - def _get_data(self): - try: - if time.strftime("%p"): - result = datetime.datetime.strptime( - f"{int(self.hour):02d}:{int(self.minute):02d} {self.am_pm}", - "%I:%M %p", - ).time() - else: - result = datetime.datetime.strptime( - f"{int(self.hour):02d}:{int(self.minute):02d}", - "%I:%M", - ).time() - return result - except ValueError: - return None # hour is zero - - def _check_orienation(self, *args, do_anim=False): - orientation = self.theme_cls.device_orientation - if self._state != "input" and orientation != self._state: - self._update_pos_size(orientation, anim=do_anim) - - def _update_pos_size(self, orientation, anim=False): - d = self.animation_duration - # time input - time_input_pos = ( - [dp(24), dp(368)] - if orientation == "portrait" - else ( - [dp(24), dp(178)] - if orientation == "landscape" - else [dp(24), dp(96)] + def _override_properties(self, *args): + if self.readonly: + self.canvas.before.get_group("rectangle-cursor-blink")[0].size = ( + 0, + 0, ) + + self.canvas.before.get_group("active-indicator-color")[0].rgba = ( + 0, + 0, + 0, + 0, ) - if anim: - _time_input = Animation( - pos=time_input_pos, - d=d, - t=self.animation_transition, # 80 - 8, - ) - _time_input.start(self._time_input) - else: - self._time_input.pos = time_input_pos - - self._time_input.disabled = False if orientation == "input" else True - self._time_input.size = ( - [dp(216), dp(62)] if orientation == "input" else [dp(216), dp(72)] - ) - Clock.schedule_once(self._time_input._update_padding) - - # Circular selector. - if orientation == "input": - if self.theme_cls.device_orientation == "portrait": - selector_pos = [dp(34), dp(-256)] - self._selector.scale_origin = [dp(162), dp(200)] - else: - selector_pos = [dp(324), dp(-19)] - self._selector.scale_origin = [dp(292), dp(109)] - elif orientation == "portrait": - self._selector.pos = selector_pos = [dp(36), dp(76)] - else: - self._selector.pos = selector_pos = [dp(304), dp(76)] - - Animation( - pos=selector_pos, - scale=0 if orientation == "input" else 1, - opacity=0 if orientation == "input" else 1, - d=d, - t=self.animation_transition, - ).start(self._selector) - - # AM/PM selector. - am_pm_pos = ( - [dp(252), dp(368)] - if orientation == "portrait" - else ( - [dp(24), dp(126)] - if orientation == "landscape" - else [dp(252), dp(96)] - ) - ) - am_pm_size = ( - [dp(52), dp(80)] - if orientation == "portrait" - else ( - [dp(216), dp(40)] - if orientation == "landscape" - else [dp(48), dp(70)] - ) - ) - if anim: - Animation( - pos=am_pm_pos, - size=am_pm_size, - d=d, - t=self.animation_transition, - ).start(self._am_pm_selector) - else: - self._am_pm_selector.pos = am_pm_pos - self._am_pm_selector.size = am_pm_size - - self._am_pm_selector.orientation = ( - "horizontal" if orientation == "landscape" else "vertical" - ) - - # MDTimePicker. - time_picker_size = ( - [dp(328), dp(500)] - if orientation == "portrait" - else ( - [dp(584), dp(368)] - if orientation == "landscape" - else [dp(324), dp(218)] - ) - ) - if anim: - Animation( - size=time_picker_size, - d=d, - t=self.animation_transition, - ).start(self) - else: - self.size = time_picker_size - - # Minute label. - Animation( - pos=[dp(144), dp(76)], - opacity=1 if orientation == "input" else 0, - d=d, - t=self.animation_transition, - ).start(self._minute_label) - - # Hour label. - Animation( - pos=[dp(24), dp(76)], - opacity=1 if orientation == "input" else 0, - d=d, - t=self.animation_transition, - ).start(self._hour_label) - - self._state = orientation - self.ids.input_clock_switch.icon = ( - "clock-time-four-outline" if orientation == "input" else "keyboard" - ) - - def _set_current_time(self, *args): - self.time = self._get_data() - - def _switch_input(self): - self._update_pos_size( - self.theme_cls.device_orientation - if self._state == "input" - else "input", - anim=True, - ) + self.canvas.before.get_group("fill-color-rounded-rectangle")[ + 0 + ].radius = [ + dp(12), + ] diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__init__.py deleted file mode 100644 index 34c14ce..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .progressbar import MDProgressBar # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index bd2de08..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__pycache__/progressbar.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__pycache__/progressbar.cpython-311.pyc deleted file mode 100644 index 9deffb7..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/__pycache__/progressbar.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/progressbar.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/progressbar.kv deleted file mode 100644 index 89aafb8..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/progressbar.kv +++ /dev/null @@ -1,34 +0,0 @@ - - canvas: - Clear - Color: - rgba: - self.theme_cls.divider_color \ - if not self.back_color else \ - self.back_color - RoundedRectangle: - radius: root.radius - size: - (self.width, self.height) \ - if self.orientation == "horizontal" else \ - (self.width, self.height) - pos: - (self.x, self.center_y - self.height / 2) \ - if self.orientation == "horizontal" else \ - (self.center_x - self.width / 2, self.y) - Color: - rgba: - self.theme_cls.primary_color if not self.color else self.color - RoundedRectangle: - radius: root.radius - size: - (self.width * self.value_normalized, self.height if self.height else dp(4)) \ - if self.orientation == "horizontal" else \ - (self.width, self.height * self.value_normalized) - pos: - (self.width * (1 - self.value_normalized) + self.x \ - if self.reversed else self.x + self._x, self.center_y - self.height / 2) \ - if self.orientation == "horizontal" \ - else (self.center_x - self.width / 2, self.height \ - * (1 - self.value_normalized) + self.y if self.reversed \ - else self.y) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/progressbar.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/progressbar.py deleted file mode 100644 index b785f03..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressbar/progressbar.py +++ /dev/null @@ -1,336 +0,0 @@ -""" -Components/ProgressBar -====================== - -.. seealso:: - - `Material Design spec, Progress indicators `_ - -.. rubric:: Progress indicators express an unspecified wait time or display - the length of a process. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-preview.png - :align: center - -`KivyMD` provides the following bars classes for use: - -- MDProgressBar_ -- Determinate_ -- Indeterminate_ - -.. MDProgressBar: -MDProgressBar -------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDBoxLayout: - padding: "10dp" - - MDProgressBar: - value: 50 - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar.png - :align: center - -Vertical orientation --------------------- - -.. code-block:: kv - - MDProgressBar: - orientation: "vertical" - value: 50 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-vertical.png - :align: center - -With custom color ------------------ - -.. code-block:: kv - - MDProgressBar: - value: 50 - color: app.theme_cls.accent_color - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-bar-custom-color.png - :align: center - -.. Indeterminate: -Indeterminate -------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDProgressBar: - id: progress - pos_hint: {"center_y": .6} - type: "indeterminate" - - MDRaisedButton: - text: "STOP" if app.state == "start" else "START" - pos_hint: {"center_x": .5, "center_y": .45} - on_press: app.state = "stop" if app.state == "start" else "start" - ''' - - - class Test(MDApp): - state = StringProperty("stop") - - def build(self): - return Builder.load_string(KV) - - def on_state(self, instance, value): - { - "start": self.root.ids.progress.start, - "stop": self.root.ids.progress.stop, - }.get(value)() - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/indeterminate-progress-bar.gif - :align: center - -.. Determinate: -Determinate ------------ - -.. code-block:: kv - - MDProgressBar: - type: "determinate" - running_duration: 1 - catching_duration: 1.5 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/determinate-progress-bar.gif - :align: center -""" - -__all__ = ("MDProgressBar",) - -import os -from typing import Union - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - OptionProperty, - StringProperty, - VariableListProperty, -) -from kivy.uix.progressbar import ProgressBar - -from kivymd import uix_path -from kivymd.theming import ThemableBehavior - -with open( - os.path.join(uix_path, "progressbar", "progressbar.kv"), encoding="utf-8" -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDProgressBar(ThemableBehavior, ProgressBar): - """ - Progressbar class. - - For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.progressbar.ProgressBar` - classes documentation. - """ - - radius = VariableListProperty([0], length=4) - """ - Progress line radius. - - .. versionadded:: 1.2.0 - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - reversed = BooleanProperty(False) - """ - Reverse the direction the progressbar moves. - - :attr:`reversed` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - orientation = OptionProperty( - "horizontal", options=["horizontal", "vertical"] - ) - """ - Orientation of progressbar. Available options are: `'horizontal '`, - `'vertical'`. - - :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'horizontal'`. - """ - - color = ColorProperty(None) - """ - Progress bar color in (r, g, b, a) or string format. - - :attr:`color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - back_color = ColorProperty(None) - """ - Progress bar back color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - :attr:`back_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - running_transition = StringProperty("in_cubic") - """ - Running transition. - - :attr:`running_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'in_cubic'`. - """ - - catching_transition = StringProperty("out_quart") - """ - Catching transition. - - :attr:`catching_transition` is an :class:`~kivy.properties.StringProperty` - and defaults to `'out_quart'`. - """ - - running_duration = NumericProperty(0.5) - """ - Running duration. - - :attr:`running_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.5`. - """ - - catching_duration = NumericProperty(0.8) - """ - Catching duration. - - :attr:`running_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.8`. - """ - - type = OptionProperty( - None, options=["indeterminate", "determinate"], allownone=True - ) - """ - Type of progressbar. Available options are: `'indeterminate '`, - `'determinate'`. - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - _x = NumericProperty(0) - - def __init__(self, **kwargs): - self.catching_anim = None - self.running_anim = None - super().__init__(**kwargs) - Clock.schedule_once(self.check_size) - - def check_size(self, interval: Union[int, float]) -> None: - if self.height == 100: - if self.orientation == "horizontal": - self.size_hint_y = None - self.height = dp(4) - elif self.orientation == "vertical": - self.size_hint_x = None - self.width = dp(4) - - def start(self) -> None: - """Start animation.""" - - if self.type in ("indeterminate", "determinate"): - Clock.schedule_once(self._set_default_value) - if not self.catching_anim and not self.running_anim: - if self.type == "indeterminate": - self._create_indeterminate_animations() - else: - self._create_determinate_animations() - self.running_away() - - def stop(self) -> None: - """Stop animation.""" - - Animation.cancel_all(self) - self._set_default_value(0) - - def running_away(self, *args) -> None: - self._set_default_value(0) - self.running_anim.start(self) - - def catching_up(self, *args) -> None: - if self.type == "indeterminate": - self.reversed = True - self.catching_anim.start(self) - - def _create_determinate_animations(self): - self.running_anim = Animation( - value=100, - opacity=1, - t=self.running_transition, - d=self.running_duration, - ) - self.running_anim.bind(on_complete=self.catching_up) - self.catching_anim = Animation( - opacity=0, - t=self.catching_transition, - d=self.catching_duration, - ) - self.catching_anim.bind(on_complete=self.running_away) - - def _create_indeterminate_animations(self): - self.running_anim = Animation( - _x=self.width / 2, - value=50, - t=self.running_transition, - d=self.running_duration, - ) - self.running_anim.bind(on_complete=self.catching_up) - self.catching_anim = Animation( - value=0, t=self.catching_transition, d=self.catching_duration - ) - self.catching_anim.bind(on_complete=self.running_away) - - def _set_default_value(self, interval): - self._x = 0 - self.value = 0 - self.reversed = False diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__init__.py new file mode 100644 index 0000000..1b0de19 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__init__.py @@ -0,0 +1,4 @@ +from .progressindicator import ( + MDCircularProgressIndicator, + MDLinearProgressIndicator, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0c46514 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__pycache__/progressindicator.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__pycache__/progressindicator.cpython-311.pyc new file mode 100644 index 0000000..8be67fc Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/__pycache__/progressindicator.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/progressindicator.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/progressindicator.kv new file mode 100644 index 0000000..8f58c3e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/progressindicator.kv @@ -0,0 +1,64 @@ + + canvas.before: + PushMatrix + Rotate: + angle: self._rotation_angle + origin: self.center + canvas: + Color: + rgba: self.color if self.color else self.theme_cls.primaryColor + a: self._alpha + SmoothLine: + cap: 'square' + width: root.line_width + circle: + self.center_x, self.center_y, self.width / 2, \ + self._angle_start, self._angle_end + canvas.after: + PopMatrix + + + + canvas: + Clear + # Inactive track. + Color: + rgba: + self.theme_cls.surfaceContainerHighestColor \ + if not self.track_color else \ + self.track_color + SmoothRoundedRectangle: + radius: root.radius + size: + (self.width, self.height) \ + if self.orientation == "horizontal" else \ + (self.width, self.height) + pos: + (self.x, self.center_y - self.height / 2) \ + if self.orientation == "horizontal" else \ + (self.center_x - self.width / 2, self.y) + # Active track. + Color: + rgba: + self.theme_cls.primaryColor \ + if not self.indicator_color else \ + self.indicator_color + SmoothRoundedRectangle: + radius: root.radius + size: + ( \ + self.width * self.value_normalized, \ + self.height if self.height else dp(4) \ + ) \ + if self.orientation == "horizontal" else \ + (self.width, self.height * self.value_normalized) + pos: + ( \ + self.width * (1 - self.value_normalized) + self.x \ + if self.reversed else self.x + self._x, \ + self.center_y - self.height / 2 \ + ) \ + if self.orientation == "horizontal" \ + else (self.center_x - self.width / 2, self.height \ + * (1 - self.value_normalized) + self.y if self.reversed \ + else self.y) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/progressindicator.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/progressindicator.py new file mode 100644 index 0000000..db71762 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/progressindicator/progressindicator.py @@ -0,0 +1,651 @@ +""" +Components/ProgressIndicator +============================ + +.. seealso:: + + `Material Design spec, ProgressIndicator `_ + +.. rubric:: Progress indicators show the status of a process in real time. + +- Use the same progress indicator for all instances of a process (like loading) +- Two types: linear and circular +- Never use them as decoration +- They capture attention through motion + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/progress-indicator-types.png + :align: center + +1. Linear progress indicator +2. Circular progress indicator + +Usage +----- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDLinearProgressIndicator: + size_hint_x: .5 + value: 50 + pos_hint: {'center_x': .5, 'center_y': .4} + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + +Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/linear-progress-indicator-usage.png + :align: center + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDCircularProgressIndicator: + size_hint: None, None + size: "48dp", "48dp" + pos_hint: {'center_x': .5, 'center_y': .5} + ''' + + + class Example(MDApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-progress-indicator-usage.gif + :align: center + +Linear progress indicators can be determinate or indeterminate. + +Determinate linear progress indicator +------------------------------------- + +Determinate operations display the indicator increasing from 0 to 100% of the +track, in sync with the process’s progress. + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDLinearProgressIndicator: + id: progress + size_hint_x: .5 + type: "determinate" + pos_hint: {'center_x': .5, 'center_y': .4} + ''' + + + class Example(MDApp): + def on_start(self): + self.root.ids.progress.start() + + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/determinate-linear-progress-indicator.gif + :align: center + +Circular progress indicators can be determinate or indeterminate. + +Indeterminate circular progress indicator +----------------------------------------- + +Indeterminate operations display the indicator continually growing and +shrinking along the track until the process is complete.. + +.. code-block:: kv + + MDCircularProgressIndicator: + size_hint: None, None + size: "48dp", "48dp" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-progress-indicator-usage.gif + :align: center + +Determinate circular progress indicator +--------------------------------------- + +.. code-block:: kv + + MDCircularProgressIndicator: + size_hint: None, None + size: "48dp", "48dp" + determinate: True + on_determinate_complete: print(args) + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/determinate-circular-progress-indicator.gif + :align: center + +API break +========= + +1.1.1 version +------------- + +.. code-block:: kv + + MDProgressBar: + value: 50 + color: app.theme_cls.accent_color + +.. code-block:: kv + + MDSpinner: + size_hint: None, None + size: dp(48), dp(48) + +2.0.0 version +------------- + +.. code-block:: kv + + MDLinearProgressIndicator: + value: 50 + indicator_color: app.theme_cls.errorColor + +.. code-block:: kv + + MDCircularProgressIndicator: + size_hint: None, None + size: dp(48), dp(48) +""" + +__all__ = ("MDCircularProgressIndicator", "MDLinearProgressIndicator") + +import os + +from kivy.animation import Animation +from kivy.clock import Clock +from kivy.lang import Builder +from kivy.metrics import dp +from kivy.properties import ( + BooleanProperty, + ColorProperty, + ListProperty, + NumericProperty, + VariableListProperty, + OptionProperty, + StringProperty, +) +from kivy.uix.progressbar import ProgressBar +from kivy.uix.widget import Widget + +from kivymd import uix_path +from kivymd.theming import ThemableBehavior +from kivymd.uix.behaviors import DeclarativeBehavior + +with open( + os.path.join(uix_path, "progressindicator", "progressindicator.kv"), + encoding="utf-8", +) as kv_file: + Builder.load_string(kv_file.read()) + + +class MDLinearProgressIndicator(DeclarativeBehavior, ThemableBehavior, ProgressBar): + """ + Implementation of the linear progress indicator. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.progressbar.ProgressBar` + classes documentation. + """ + + radius = VariableListProperty([0], length=4) + """ + Progress line radius. + + .. versionadded:: 1.2.0 + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ + + reversed = BooleanProperty(False) + """ + Reverse the direction the progressbar moves. + + :attr:`reversed` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + orientation = OptionProperty( + "horizontal", options=["horizontal", "vertical"] + ) + """ + Orientation of progressbar. Available options are: `'horizontal '`, + `'vertical'`. + + :attr:`orientation` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'horizontal'`. + """ + + indicator_color = ColorProperty(None) + """ + Color of the active track. + + .. versionchanged:: 2.0.0 + + Rename from `color` to `indicator_color` attribute. + + :attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + track_color = ColorProperty(None) + """ + Progress bar back color in (r, g, b, a) or string format. + + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + + Rename from `back_color` to `track_color` attribute. + + :attr:`track_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + running_determinate_transition = StringProperty("out_quart") + """ + Running transition. + + .. versionchanged:: 2.0.0 + + Rename from `running_transition` to `running_determinate_transition` + attribute. + + :attr:`running_determinate_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_quart'`. + """ + + catching_determinate_transition = StringProperty("out_quart") + """ + Catching transition. + + .. versionchanged:: 2.0.0 + + Rename from `catching_transition` to `catching_determinate_transition` + attribute. + + :attr:`catching_determinate_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_quart'`. + """ + + running_determinate_duration = NumericProperty(2.5) + """ + Running duration. + + .. versionchanged:: 2.0.0 + + Rename from `running_duration` to `running_determinate_duration` + attribute. + + :attr:`running_determinate_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `2.5`. + """ + + catching_determinate_duration = NumericProperty(0.8) + """ + Catching duration. + + :attr:`running_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.8`. + """ + + type = OptionProperty( + None, options=["indeterminate", "determinate"], allownone=True + ) + """ + Type of progressbar. Available options are: `'indeterminate '`, + `'determinate'`. + + :attr:`type` is an :class:`~kivy.properties.OptionProperty` + and defaults to `None`. + """ + + running_indeterminate_transition = StringProperty("in_cubic") + """ + Running transition. + + :attr:`running_indeterminate_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'in_cubic'`. + """ + + catching_indeterminate_transition = StringProperty("out_quart") + """ + Catching transition. + + :attr:`catching_indeterminate_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_quart'`. + """ + + running_indeterminate_duration = NumericProperty(0.5) + """ + Running duration. + + :attr:`running_indeterminate_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.5`. + """ + + catching_indeterminate_duration = NumericProperty(0.8) + """ + Catching duration. + + :attr:`catching_indeterminate_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.8`. + """ + + _x = NumericProperty(0) + + def __init__(self, **kwargs): + self.catching_anim = None + self.running_anim = None + super().__init__(**kwargs) + Clock.schedule_once(self.check_size) + + def check_size(self, *args) -> None: + if self.height == 100: + if self.orientation == "horizontal": + self.size_hint_y = None + self.height = dp(4) + elif self.orientation == "vertical": + self.size_hint_x = None + self.width = dp(4) + + def start(self) -> None: + """Start animation.""" + + if self.type in ("indeterminate", "determinate"): + Clock.schedule_once(self._set_default_value) + if not self.catching_anim and not self.running_anim: + if self.type == "indeterminate": + self._create_indeterminate_animations() + else: + self._create_determinate_animations() + self.running_away() + + def stop(self) -> None: + """Stop animation.""" + + Animation.cancel_all(self) + self._set_default_value(0) + + def running_away(self, *args) -> None: + self._set_default_value(0) + self.running_anim.start(self) + + def catching_up(self, *args) -> None: + if self.type == "indeterminate": + self.reversed = True + self.catching_anim.start(self) + + # FIXME: This method fixes a bug: the indicator values are not set to 0. + def on_value(self, instance, value): + if not value: + self.value = 0.01 + + def _create_determinate_animations(self): + self.running_anim = Animation( + value=100, + opacity=1, + t=self.running_determinate_transition, + d=self.running_determinate_duration, + ) + self.running_anim.bind(on_complete=self.catching_up) + self.catching_anim = Animation( + opacity=0, + t=self.catching_determinate_transition, + d=self.catching_determinate_duration, + ) + self.catching_anim.bind(on_complete=self.running_away) + + def _create_indeterminate_animations(self): + self.running_anim = Animation( + _x=self.width / 2, + value=50, + t=self.running_indeterminate_transition, + d=self.running_indeterminate_duration, + ) + self.running_anim.bind(on_complete=self.catching_up) + self.catching_anim = Animation( + value=0, + t=self.catching_indeterminate_transition, + d=self.catching_indeterminate_duration, + ) + self.catching_anim.bind(on_complete=self.running_away) + + def _set_default_value(self, interval): + self._x = 0 + self.opacity = 1 + self.value = 0 + self.reversed = False + + +class MDCircularProgressIndicator(ThemableBehavior, Widget): + """ + Implementation of the circular progress indicator. + + .. versionchanged:: 2.0.0 + + Rename `MDSpinner` to `MDCircularProgressIndicator` class. + + For more information, see in the + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.widget.Widget` + classes documentation. + + It can be used either as an indeterminate indicator that loops while + the user waits for something to happen, or as a determinate indicator. + + Set :attr:`determinate` to **True** to activate determinate mode, and + :attr:`determinate_time` to set the duration of the animation. + + :Events: + `on_determinate_complete` + The event is called at the end of the indicator loop in the + `determinate = True` mode. + """ + + determinate = BooleanProperty(False) + """ + Determinate value. + + :attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + determinate_time = NumericProperty(2) + """ + Determinate time value. + + :attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty` + and defaults to `2`. + """ + + line_width = NumericProperty(dp(2.25)) + """ + Progress line width of indicator. + + :attr:`line_width` is a :class:`~kivy.properties.NumericProperty` + and defaults to `dp(2.25)`. + """ + + active = BooleanProperty(True) + """ + Use :attr:`active` to start or stop the indicator. + + :attr:`active` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + + color = ColorProperty(None) + """ + Indicator color in (r, g, b, a) or string format. + + :attr:`color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + palette = ListProperty() + """ + A set of colors. Changes with each completed indicator cycle. + + :attr:`palette` is a :class:`~kivy.properties.ListProperty` + and defaults to `[]`. + """ + + _alpha = NumericProperty(0) + _rotation_angle = NumericProperty(360) + _angle_start = NumericProperty(0) + _angle_end = NumericProperty(0) + _palette = [] + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._alpha_anim_in = Animation(_alpha=1, duration=0.8, t="out_quad") + self._alpha_anim_out = Animation(_alpha=0, duration=0.3, t="out_quad") + self._alpha_anim_out.bind( + on_complete=self._reset, + on_progress=self._on_determinate_progress, + ) + self.register_event_type("on_determinate_complete") + Clock.schedule_once(self.check_determinate) + + def on__rotation_angle(self, *args): + if self._rotation_angle == 0: + self._rotation_angle = 360 + if not self.determinate: + _rot_anim = Animation(_rotation_angle=0, duration=2) + _rot_anim.start(self) + elif self._rotation_angle == 360: + if self._palette: + try: + Animation(color=next(self._palette), duration=2).start(self) + except StopIteration: + self._palette = iter(self.palette) + Animation(color=next(self._palette), duration=2).start(self) + + def on_palette(self, instance, palette_list: list) -> None: + """Fired when the `palette` value changes.""" + + self._palette = iter(palette_list) + + def on_active(self, instance, value) -> None: + """Fired when the `active` value changes.""" + + self._reset() + if self.active: + self.check_determinate() + + def on_determinate_complete(self, *args) -> None: + """ + The event is fired at the end of the indicator loop in the + `determinate = True` mode. + """ + + self._angle_start = 0 + self._angle_end = 0 + self._rotation_angle = 360 + self._alpha = 0 + Clock.schedule_once(self._start_determinate) + + def check_determinate(self, *args) -> None: + """Fired when the class is initialized.""" + + if self.active: + if self.determinate: + self._start_determinate() + else: + self._start_loop() + + def _update_color(self, *args): + self.color = self.theme_cls.primaryColor + + def _start_determinate(self, *args): + self._alpha_anim_in.start(self) + Animation( + _rotation_angle=0, + duration=self.determinate_time * 0.7, + t="out_quad", + ).start(self) + + _angle_start_anim = Animation( + _angle_end=360, duration=self.determinate_time, t="in_out_quad" + ) + _angle_start_anim.bind( + on_complete=lambda *x: self._alpha_anim_out.start(self) + ) + _angle_start_anim.start(self) + + def _start_loop(self, *args): + if self._alpha == 0: + _rot_anim = Animation(_rotation_angle=0, duration=2, t="linear") + _rot_anim.start(self) + + self._alpha = 1 + self._alpha_anim_in.start(self) + _angle_start_anim = Animation( + _angle_end=self._angle_end + 270, duration=0.6, t="in_out_cubic" + ) + _angle_start_anim.bind(on_complete=self._anim_back) + _angle_start_anim.start(self) + + def _anim_back(self, *args): + _angle_back_anim = Animation( + _angle_start=self._angle_end - 8, duration=0.6, t="in_out_cubic" + ) + _angle_back_anim.bind(on_complete=self._start_loop) + + _angle_back_anim.start(self) + + def _reset(self, *args): + Animation.cancel_all( + self, + "_angle_start", + "_rotation_angle", + "_angle_end", + "_alpha", + "color", + ) + self._angle_start = 0 + self._angle_end = 0 + self._rotation_angle = 360 + self._alpha = 0 + + def _on_determinate_progress(self, animation, instance, value): + if value == 1: + self.dispatch("on_determinate_complete") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recyclegridlayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recyclegridlayout.py index e519af5..6f98df6 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recyclegridlayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recyclegridlayout.py @@ -16,7 +16,7 @@ RecycleGridLayout canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -28,7 +28,7 @@ MDRecycleGridLayout MDRecycleGridLayout: adaptive_height: True - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor Available options are: ---------------------- @@ -38,6 +38,7 @@ Available options are: - adaptive_size_ .. adaptive_height: + adaptive_height --------------- @@ -53,6 +54,7 @@ Equivalent height: self.minimum_height .. adaptive_width: + adaptive_width -------------- @@ -68,6 +70,7 @@ Equivalent width: self.minimum_width .. adaptive_size: + adaptive_size ------------- @@ -87,13 +90,24 @@ from kivy.uix.recyclegridlayout import RecycleGridLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDRecycleGridLayout( - DeclarativeBehavior, ThemableBehavior, RecycleGridLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + RecycleGridLayout, + MDAdaptiveWidget, ): """ - Recycle grid layout layout class. For more information, see in the - :class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` class documentation. + Recycle grid layout class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.recyclegridlayout.RecycleGridLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recycleview.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recycleview.py index ddcf16c..e8467e8 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recycleview.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/recycleview.py @@ -16,7 +16,7 @@ RecycleView canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -27,7 +27,7 @@ MDRecycleView .. code-block:: kv MDRecycleView: - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor """ __all__ = ("MDRecycleView",) @@ -36,13 +36,24 @@ from kivy.uix.recycleview import RecycleView from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDRecycleView( - DeclarativeBehavior, ThemableBehavior, RecycleView, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + RecycleView, + MDAdaptiveWidget, ): """ - Recycle view class. For more information, see in the - :class:`~kivy.uix.recycleview.RecycleView` class documentation. + Recycle view class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.recycleview.RecycleView` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/__init__.cpython-311.pyc index 86a3e46..e159de8 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/refreshlayout.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/refreshlayout.cpython-311.pyc index 89dd690..badf71e 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/refreshlayout.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/__pycache__/refreshlayout.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/refreshlayout.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/refreshlayout.kv index e58b7f6..e69de29 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/refreshlayout.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/refreshlayout/refreshlayout.kv @@ -1,27 +0,0 @@ -#:import Window kivy.core.window.Window - - - - - AnchorLayout: - id: body_spinner - size_hint: None, None - size: dp(46), dp(46) - y: Window.height - pos_hint: {'center_x': .5} - anchor_x: 'center' - anchor_y: 'center' - - canvas: - Clear - Color: - rgba: root.circle_color - Ellipse: - pos: self.pos - size: self.size - - MDSpinner: - id: spinner - size_hint: None, None - size: dp(30), dp(30) - color: root.spinner_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/relativelayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/relativelayout.py index b0e58e1..fc6b8c6 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/relativelayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/relativelayout.py @@ -13,7 +13,7 @@ RelativeLayout RelativeLayout: canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor RoundedRectangle: pos: (0, 0) size: self.size @@ -26,20 +26,33 @@ MDRelativeLayout MDRelativeLayout: radius: [25, ] - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor """ +__all__ = ("MDRelativeLayout",) + from kivy.uix.relativelayout import RelativeLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDRelativeLayout( - DeclarativeBehavior, ThemableBehavior, RelativeLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + RelativeLayout, + MDAdaptiveWidget, ): """ - Relative layout class. For more information, see in the - :class:`~kivy.uix.relativelayout.RelativeLayout` class documentation. + Relative layout class. + + For more information see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.relativelayout.RelativeLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screen.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screen.py index 1f24676..caa0689 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screen.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screen.py @@ -13,7 +13,7 @@ Screen Screen: canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor RoundedRectangle: pos: self.pos size: self.size @@ -26,7 +26,7 @@ MDScreen MDScreen: radius: [25, 0, 0, 0] - md_bg_color: app.theme_cls.primary_color + md_bg_color: self.theme_cls.primaryColor """ from kivy.properties import ListProperty, ObjectProperty @@ -34,15 +34,28 @@ from kivy.uix.screenmanager import Screen from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior from kivymd.uix.hero import MDHeroTo -class MDScreen(DeclarativeBehavior, ThemableBehavior, Screen, MDAdaptiveWidget): +class MDScreen( + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + Screen, + MDAdaptiveWidget, +): """ Screen is an element intended to be used with a - :class:`~kivymd.uix.screenmanager.MDScreenManager`. For more information, - see in the :class:`~kivy.uix.screenmanager.Screen` class documentation. + :class:`~kivymd.uix.screenmanager.MDScreenManager`. + + For more information see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.screenmanager.Screen` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ hero_to = ObjectProperty(deprecated=True) @@ -71,7 +84,7 @@ class MDScreen(DeclarativeBehavior, ThemableBehavior, Screen, MDAdaptiveWidget): """ def on_hero_to(self, screen, widget: MDHeroTo) -> None: - """Called when the value of the :attr:`hero_to` attribute changes.""" + """Fired when the value of the :attr:`hero_to` attribute changes.""" if not isinstance(widget, MDHeroTo) or not issubclass( widget.__class__, MDHeroTo diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screenmanager.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screenmanager.py index dcc4792..e99af3d 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screenmanager.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/screenmanager.py @@ -29,18 +29,30 @@ from kivy.clock import Clock from kivy.properties import ListProperty, StringProperty from kivy.uix.screenmanager import ScreenManager -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.theming import ThemableBehavior +from kivymd.uix import MDAdaptiveWidget +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior from kivymd.uix.hero import MDHeroFrom -class MDScreenManager(DeclarativeBehavior, ScreenManager): +class MDScreenManager( + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + ScreenManager, + MDAdaptiveWidget, +): """ Screen manager. This is the main class that will control your :class:`~kivymd.uix.screen.MDScreen` stack and memory. - For more - information, see in the :class:`~kivy.uix.screenmanager.ScreenManager` - class documentation. + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.screenmanager.ScreenManager` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ current_hero = StringProperty(None, deprecated=True) @@ -109,7 +121,7 @@ class MDScreenManager(DeclarativeBehavior, ScreenManager): def on_current_hero(self, instance, value: str) -> None: """ - Called when the value of the :attr:`current_hero` attribute changes. + Fired when the value of the :attr:`current_hero` attribute changes. """ Logger.warning( diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/scrollview.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/scrollview.py index 0b00061..aaea955 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/scrollview.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/scrollview.py @@ -4,8 +4,9 @@ Components/ScrollView .. versionadded:: 1.0.0 -:class:`~kivy.uix.scrollview.ScrollView` class equivalent. Simplifies working -with some widget properties. For example: +:class:`~kivy.uix.scrollview.ScrollView` class equivalent. +It implements Material Design's overscorll effect and +simplifies working with some widget properties. For example: ScrollView ---------- @@ -16,7 +17,7 @@ ScrollView canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -27,23 +28,310 @@ MDScrollView .. code-block:: kv MDScrollView: - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor + +The stretching effect +--------------------- + +.. code-block:: python + + import os + import sys + + from kivy.core.window import Window + from kivy import __version__ as kv__version__ + from kivy.lang import Builder + from kivy.metrics import dp + + from kivymd.app import MDApp + from kivymd import __version__ + from kivymd.uix.list import ( + MDListItem, + MDListItemHeadlineText, + MDListItemSupportingText, + MDListItemLeadingIcon, + ) + + from materialyoucolor import __version__ as mc__version__ + + from examples.common_app import CommonApp + + MAIN_KV = ''' + MDScreen: + md_bg_color: app.theme_cls.backgroundColor + + MDScrollView: + do_scroll_x: False + + MDBoxLayout: + id: main_scroll + orientation: "vertical" + adaptive_height: True + + MDBoxLayout: + adaptive_height: True + + MDLabel: + theme_font_size: "Custom" + text: "OS Info" + font_size: "55sp" + adaptive_height: True + padding: "10dp", "20dp", 0, 0 + + MDIconButton: + icon: "menu" + on_release: app.open_menu(self) + pos_hint: {"center_y": .5} + ''' + + + class Example(MDApp, CommonApp): + def build(self): + self.theme_cls.theme_style = "Dark" + return Builder.load_string(MAIN_KV) + + def on_start(self): + info = { + "Name": [ + os.name, + ( + "microsoft" + if os.name == "nt" + else ("linux" if os.uname()[0] != "Darwin" else "apple") + ), + ], + "Architecture": [os.uname().machine, "memory"], + "Hostname": [os.uname().nodename, "account"], + "Python Version": ["v" + sys.version, "language-python"], + "Kivy Version": ["v" + kv__version__, "alpha-k-circle-outline"], + "KivyMD Version": ["v" + __version__, "material-design"], + "MaterialYouColor Version": ["v" + mc__version__, "invert-colors"], + "Pillow Version": ["Unknown", "image"], + "Working Directory": [os.getcwd(), "folder"], + "Home Directory": [os.path.expanduser("~"), "folder-account"], + "Environment Variables": [os.environ, "code-json"], + } + + try: + from PIL import __version__ as pil__version_ + + info["Pillow Version"] = ["v" + pil__version_, "image"] + except Exception: + pass + + for info_item in info: + self.root.ids.main_scroll.add_widget( + MDListItem( + MDListItemLeadingIcon( + icon=info[info_item][1], + ), + MDListItemHeadlineText( + text=info_item, + ), + MDListItemSupportingText( + text=str(info[info_item][0]), + ), + pos_hint={"center_x": .5, "center_y": .5}, + ) + ) + + Window.size = [dp(350), dp(600)] + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/stretch_over_scroll_stencil.gif + :align: center """ -__all__ = ("MDScrollView",) +from __future__ import annotations +__all__ = ("MDScrollView", "StretchOverScrollStencil") + +import math + +from kivy.animation import Animation +from kivy.effects.scroll import ScrollEffect +from kivy.graphics import Color, PopMatrix, PushMatrix, Scale from kivy.uix.scrollview import ScrollView -from kivymd.uix.behaviors import ( - DeclarativeBehavior, - SpecificBackgroundColorBehavior, -) +from kivymd.uix.behaviors import BackgroundColorBehavior, DeclarativeBehavior -class MDScrollView( - DeclarativeBehavior, SpecificBackgroundColorBehavior, ScrollView -): +class StretchOverScrollStencil(ScrollEffect): """ - ScrollView class. For more information, see in the - :class:`~kivy.uix.scrollview.ScrollView` class documentation. + Stretches the view on overscroll and absorbs + velocity at start and end to convert to stretch. + + .. note:: This effect only works with + :class:`kivymd.uix.scrollview.MDScrollView`. + + If you need any documentation please look at + :class:`~kivy.effects.dampedscrolleffect`. """ + + # Android constants. + minimum_absorbed_velocity = 0 + maximum_velocity = 10000 + stretch_intensity = 0.016 + exponential_scalar = math.e / (1 / 3) + scroll_friction = 0.015 + # Used in `absorb_impact` but for now it's not compatible with kivy so we + # using are approx value. + # fling_friction = 1.01 + approx_normailzer = 2e5 + + # Duration to normalize scale + # when touch up is received and view is stretched. + duration_normailzer = 10 + + scroll_view = None # scroll view instance + scroll_scale = None # Scale instruction instance + + scale_axis = "y" # axis of effect + last_touch_pos = None # used to calculate distance + + def clamp(self, value, min_val=0, max_val=0): + return min(max(value, min_val), max_val) + + def __init__(self, *arg, **kwargs): + super().__init__(*arg, **kwargs) + self.friction = self.scroll_friction + + def is_top_or_bottom(self): + return getattr(self.scroll_view, "scroll_" + self.scale_axis) in [1, 0] + + _should_absorb = True + + def on_value(self, stencil, scroll_distance): + super().on_value(stencil, scroll_distance) + if self.target_widget: + if not all([self.scroll_view, self.scroll_scale]): + self.scroll_view = self.target_widget.parent + self.scroll_scale = self.scroll_view._internal_scale + + if self.is_top_or_bottom(): + if ( + abs(self.velocity) > self.minimum_absorbed_velocity + and self._should_absorb # only first time when reaches + # top or bottom + ): + self.absorb_impact() + self._should_absorb = False + else: + self._should_absorb = True + + def get_hw(self): + return "height" if self.scale_axis == "y" else "width" + + def set_scale_origin(self): + # Check if target size is small than scrollview + # if yes don't stretch scroll view. + if getattr(self.target_widget, self.get_hw()) < getattr( + self.scroll_view, self.get_hw() + ): + return False + + self.scroll_scale.origin = [ + 0 if self.scroll_view.scroll_x <= 0.5 else self.scroll_view.width, + 0 if self.scroll_view.scroll_y <= 0.5 else self.scroll_view.height, + ] + return True + + def absorb_impact(self): + self.set_scale_origin() + sanitized_velocity = self.clamp( + abs(self.velocity), 1, self.maximum_velocity + ) + # Approx implementation. + new_scale = 1 + min( + (sanitized_velocity / self.approx_normailzer), + 1 / 3, + ) + init_anim = Animation( + **{self.scale_axis: new_scale}, + d=(sanitized_velocity * 4) / 1e6, + ) + init_anim.bind(on_complete=self.reset_scale) + init_anim.start(self.scroll_scale) + + def get_component(self, pos): + return pos[-1 if self.scale_axis == "y" else 1] + + def convert_overscroll(self, touch): + if ( + self.scroll_view + and self.scroll_view.collide_point(*touch.pos) + and self.is_top_or_bottom() + and getattr(self.scroll_view, "do_scroll_" + self.scale_axis) + and self.velocity == 0 + and self.set_scale_origin() # sets stretch direction + ): + # Distance travelled by touch divided by size of scrollview. + distance = ( + abs( + self.get_component(touch.pos) + - self.get_component(self.last_touch_pos) + ) + / self.scroll_view.height + ) + # Constant scale due to distance. + linear_intensity = self.stretch_intensity * distance + # Far the touch -> less it stretches. + exponential_intensity = self.stretch_intensity * ( + 1 - math.exp(-distance * self.exponential_scalar) + ) + new_scale = 1 + exponential_intensity + linear_intensity + setattr(self.scroll_scale, self.scale_axis, new_scale) + + def reset_scale(self, *arg): + if not self.scroll_scale: + return + _scale = getattr(self.scroll_scale, self.scale_axis) + if _scale > 1: + anim = Animation( + **{self.scale_axis: 1}, + d=0.2, + ) + anim.start(self.scroll_scale) + + +class MDScrollView(DeclarativeBehavior, BackgroundColorBehavior, ScrollView): + """ + An approximate implementation to Material Design's overscorll effect. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.scrollview.ScrollView` + classes documentation. + """ + + _internal_scale = None + + def __init__(self, *args, **kwargs): + self.effect_cls = StretchOverScrollStencil + super().__init__(*args, **kwargs) + with self.canvas.before: + Color(rgba=self.md_bg_color) + PushMatrix() + self._internal_scale = Scale() + with self.canvas.after: + PopMatrix() + self.effect_y.scale_axis = "y" + self.effect_x.scale_axis = "x" + + def on_touch_down(self, touch): + self.effect_x.last_touch_pos = touch.pos + self.effect_y.last_touch_pos = touch.pos + super().on_touch_down(touch) + + def on_touch_move(self, touch): + self.effect_x.convert_overscroll(touch) + self.effect_y.convert_overscroll(touch) + super().on_touch_move(touch) + + def on_touch_up(self, touch): + self.effect_x.reset_scale() + self.effect_y.reset_scale() + super().on_touch_up(touch) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__init__.py index f516e1c..91ad5be 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__init__.py @@ -1,4 +1,6 @@ from .segmentedbutton import ( # NOQA F401 MDSegmentedButton, MDSegmentedButtonItem, + MDSegmentButtonIcon, + MDSegmentButtonLabel, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/__init__.cpython-311.pyc index d0a7ef0..6ca8d89 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/segmentedbutton.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/segmentedbutton.cpython-311.pyc index e5d22b1..2eeccf6 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/segmentedbutton.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/__pycache__/segmentedbutton.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.kv index c4b63ff..23ad70e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.kv @@ -1,32 +1,124 @@ - size_hint: None, None - height: "40dp" - opacity: 0 + size_hint_y: None + height: self.minimum_height + radius: [self.height / 2, ] + + # FIXME: The use of an additional container is due to the Kivy bug - + # https://github.com/kivy/kivy/issues/8470 + MDSegmentedButtonContainer: + id: container + size_hint: 1, None + opacity: 1 + size_hint_min_x: 0 + height: + { \ + "large": "40dp", \ + "normal": "36dp", \ + "medium": "32dp", \ + "small": "28dp", \ + }[root.type] + + + + font_style: "Label" + role: "large" + theme_line_height: "Custom" + line_height: 1 + markup: True + adaptive_width: True + pos_hint: {"center_y": .5} + padding: "12dp", 0, "12dp", 0 + text_color: + ( \ + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.ids.container.parent.active else \ + self.theme_cls.onSurfaceColor \ + ) \ + if self.theme_text_color == "Primary" else \ + ( \ + self.text_color \ + if self.text_color else app.theme_cls.transparentColor \ + ) \ + ) \ + if self.parent else app.theme_cls.transparentColor + + + + theme_font_size: "Custom" + font_size: "18sp" + pos_hint: {"center_y": .5} + padding: + "12dp", \ + 0, \ + 0 \ + if self.parent.parent.ids.container.parent._label else \ + "12dp", \ + 0 + icon_color: + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self.parent.parent.ids.container.parent.active else \ + self.theme_cls.onSurfaceColor \ + ) \ + if self.theme_icon_color == "Primary" else self.icon_color - size_hint: None, None - height: self.parent.height + md_bg_color: + ( \ + self.theme_cls.transparentColor \ + if not self.active else \ + ( \ + self.theme_cls.secondaryContainerColor \ + if not self.selected_color else self.selected_color \ + ) \ + ) \ + if not self.disabled else \ + ( \ + ( \ + self.theme_cls.onSurfaceColor[:-1] \ + + [self.segmented_button_opacity_value_disabled_container] \ + if not self.md_bg_color_disabled else self.md_bg_color_disabled \ + ) \ + if not self.active else \ + ( \ + self.md_bg_color[:-1] \ + + [self.segmented_button_opacity_value_disabled_container_active] \ + if not self.md_bg_color_disabled else self.md_bg_color_disabled \ + ) \ + ) line_color: - self.theme_cls.disabled_hint_text_color \ - if self.parent.line_color == [0, 0, 0, 0] else \ - self.parent.line_color + ( \ + self.theme_cls.outlineColor \ + if self.theme_line_color == "Primary" else \ + ( \ + self._line_color \ + if self._line_color else \ + self.line_color \ + ) \ + ) \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + \ + [self.segmented_button_opacity_value_disabled_line] - SegmentButtonIcon: - id: scale_icon - icon: root.icon - size_hint: None, None - size: "24dp", "24dp" - pos_hint: {"center_y": .5} - scale_value_x: 1 if root.icon else 0 - scale_value_y: 1 if root.icon else 0 - x: label_text.x - dp(32) + MDSegmentedButtonItemContainer: + id: container + size_hint_x: None + width: self.minimum_width + pos_hint: {"center_x": .5} - MDLabel: - id: label_text - text: root.text - adaptive_size: True - pos_hint: {"center_y": .5} - x: - root.center_x - (self.texture_size[0] / 2) \ - + (dp(16) if root.icon else 0) + + + icon: "check" + pos_hint: {"center_y": .5} + theme_font_size: "Custom" + font_size: 0 + icon_color: + ( \ + self.theme_cls.onSecondaryContainerColor \ + if self._item.active else \ + self.theme_cls.onSurfaceColor \ + ) \ + if not self._segmented_button.selected_icon_color else \ + self._segmented_button.selected_icon_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.py index 0729dba..4b74237 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedbutton/segmentedbutton.py @@ -16,66 +16,120 @@ Components/SegmentedButton .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-preview.png :align: center -Usage +- Segmented buttons can contain icons, label text, or both +- Two types: single-select and multi-select +- Use for simple choices between two to five items (for more items or complex + choices, use chips) + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-types.png + :align: center + +1. Single-select segmented button +2. Multi-select segmented button + +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-anatomy.png + :align: center + +Icons ----- +Icons may be used as labels by themselves or alongside text. +If an icon is used without label text, it must clearly communicate the option +it represents. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-icons.png + :align: center + +Use with text and icon +---------------------- + .. code-block:: kv - MDScreen: + MDSegmentedButton: - MDSegmentedButton: + MDSegmentedButtonItem: - MDSegmentedButtonItem: - icon: ... - text: ... + MDSegmentButtonIcon: + icon: "language-python" - MDSegmentedButtonItem: - icon: ... - text: ... + MDSegmentButtonLabel: + text: "Python" - MDSegmentedButtonItem: - icon: ... - text: ... + MDSegmentedButtonItem: -Example -------- + MDSegmentButtonIcon: + icon: "language-javascript" -.. code-block:: python + MDSegmentButtonLabel: + text: "Java-Script" - from kivy.lang import Builder + MDSegmentedButtonItem: - from kivymd.app import MDApp + MDSegmentButtonIcon: + icon: "language-swift" - KV = ''' - MDScreen: + MDSegmentButtonLabel: + text: "Swift" - MDSegmentedButton: - pos_hint: {"center_x": .5, "center_y": .5} - - MDSegmentedButtonItem: - text: "Walking" - - MDSegmentedButtonItem: - text: "Transit" - - MDSegmentedButtonItem: - text: "Driving" - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - -By default, segmented buttons support single marking of elements: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-false.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-use-with-text-and-icon.gif :align: center +Use without text with an icon +----------------------------- + +.. code-block:: kv + + MDSegmentedButton: + + MDSegmentedButtonItem: + + MDSegmentButtonIcon: + icon: "language-python" + + MDSegmentedButtonItem: + + MDSegmentButtonIcon: + icon: "language-javascript" + + MDSegmentedButtonItem: + + MDSegmentButtonIcon: + icon: "language-swift" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-use-without-text-with-an-icon.gif + :align: center + +Use only text +------------- + +.. code-block:: kv + + MDSegmentedButton: + + MDSegmentedButtonItem: + + MDSegmentButtonLabel: + text: "Python" + + MDSegmentedButtonItem: + + MDSegmentButtonLabel: + text: "Java-Script" + + MDSegmentedButtonItem: + + MDSegmentButtonLabel: + text: "Swift" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-use-only-text.gif + :align: center + +Multiselect +----------- + For multiple marking of elements, use the :attr:`kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButton.multiselect` parameter: @@ -88,59 +142,83 @@ parameter: .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-multiselect-true.gif :align: center -Control width -------------- +Type +---- -The width of the panel of segmented buttons will be equal to the width -of the texture of the widest button multiplied by the number of buttons: +Density can be used in denser UIs where space is limited. Density is only +applied to the height. Each step down in density removes 4dp from the height. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-width-by-default.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-type.png :align: center -But you can use the `size_hint_x` parameter to specify the relative width: - -.. code-block:: kv - - MDSegmentedButton: - size_hint_x: .9 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-width-size-hint-x.png - :align: center - -Customization -------------- - -You can see below in the documentation from which classes the -:class:`~kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButton` and -:class:`~kivymd.uix.segmentedbutton.segmentedbutton.MDSegmentedButtonItem` -classes are inherited and use all their attributes such as -`md_bg_color`, `md_bg_color` etc. for additional customization of segments. - -Events ------- - -- on_marked - The method is called when a segment is marked. - -- on_unmarked - The method is called when a segment is unmarked. - -.. code-block:: kv - - MDSegmentedButton: - on_marked: app.on_marked(*args) - .. code-block:: python - def on_marked( - self, - segment_button: MDSegmentedButton, - segment_item: MDSegmentedButtonItem, - marked: bool, - ) -> None: - print(segment_button) - print(segment_item) - print(marked) + from kivy.lang import Builder + + from kivymd.uix.label import MDLabel + from kivymd.uix.boxlayout import MDBoxLayout + from kivymd.uix.segmentedbutton import ( + MDSegmentedButton, + MDSegmentedButtonItem, + MDSegmentButtonLabel, + ) + from kivymd.app import MDApp + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDBoxLayout: + id: box + orientation: "vertical" + size_hint_x: .7 + adaptive_height: True + spacing: "24dp" + pos_hint: {"center_x": .5, "center_y": .5} + ''' + + + class Example(MDApp): + def on_start(self): + for segment_type in ["large", "normal", "medium", "small"]: + self.root.ids.box.add_widget( + MDBoxLayout( + MDLabel( + text=f"Type '{segment_type}'", + adaptive_height=True, + bold=True, + pos_hint={"center_y": 0.5}, + halign="center", + ), + MDSegmentedButton( + MDSegmentedButtonItem( + MDSegmentButtonLabel( + text="Songs", + ), + ), + MDSegmentedButtonItem( + MDSegmentButtonLabel( + text="Albums", + ), + ), + MDSegmentedButtonItem( + MDSegmentButtonLabel( + text="Podcasts", + ), + ), + type=segment_type, + ), + orientation="vertical", + spacing="12dp", + adaptive_height=True, + ) + ) + + def build(self): + return Builder.load_string(KV) + + + Example().run() A practical example ------------------- @@ -157,37 +235,31 @@ A practical example from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.segmentedbutton import MDSegmentedButton, MDSegmentedButtonItem - from kivymd.utils import asynckivy + + import asynckivy KV = ''' adaptive_height: True - md_bg_color: "#343930" radius: 16 - TwoLineAvatarListItem: - id: item - divider: None - _no_ripple_effect: True - text: root.name - secondary_text: root.path_to_file - theme_text_color: "Custom" - text_color: "#8A8D79" - secondary_theme_text_color: self.theme_text_color - secondary_text_color: self.text_color - on_size: - self.ids._left_container.size = (item.height, item.height) - self.ids._left_container.x = dp(6) - self._txt_right_pad = item.height + dp(12) + MDListItem: + radius: 16 + theme_bg_color: "Custom" + md_bg_color: self.theme_cls.secondaryContainerColor - ImageLeftWidget: + MDListItemLeadingAvatar: source: root.album - radius: root.radius + + MDListItemHeadlineText: + text: root.name + + MDListItemSupportingText: + text: root.path_to_file MDScreen: - md_bg_color: "#151514" + md_bg_color: self.theme_cls.backgroundColor MDBoxLayout: orientation: "vertical" @@ -197,25 +269,31 @@ A practical example MDLabel: adaptive_height: True text: "Your downloads" - font_style: "H5" - theme_text_color: "Custom" - text_color: "#8A8D79" + theme_font_style: "Custom" + font_style: "Display" + role: "small" MDSegmentedButton: size_hint_x: 1 - selected_color: "#303A29" - line_color: "#343930" - on_marked: app.on_marked(*args) MDSegmentedButtonItem: - text: "Songs" - active: True + on_active: app.generate_card() + + MDSegmentButtonLabel: + text: "Songs" + active: True MDSegmentedButtonItem: - text: "Albums" + on_active: app.generate_card() + + MDSegmentButtonLabel: + text: "Albums" MDSegmentedButtonItem: - text: "Podcasts" + on_active: app.generate_card() + + MDSegmentButtonLabel: + text: "Podcasts" RecycleView: id: card_list @@ -242,16 +320,9 @@ A practical example class Example(MDApp): def build(self): self.theme_cls.theme_style = "Dark" + self.theme_cls.primary_palette = "Olive" return Builder.load_string(KV) - def on_marked( - self, - segment_button: MDSegmentedButton, - segment_item: MDSegmentedButtonItem, - marked: bool, - ) -> None: - self.generate_card() - def generate_card(self): async def generate_card(): for i in range(10): @@ -273,68 +344,126 @@ A practical example .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-button-practical-example.gif :align: center + +API break +========= + +1.2.0 version +------------- + +.. code-block:: kv + + MDSegmentedButton: + on_marked: func(*args) + + MDSegmentedButtonItem: + icon: ... + text: ... + +2.0.0 version +------------- + +.. code-block:: kv + + MDSegmentedButton: + + MDSegmentedButtonItem: + on_active: func(*args) + + MDSegmentButtonIcon: + icon: ... + + MDSegmentButtonLabel: + text: ... + """ from __future__ import annotations -__all__ = ("MDSegmentedButton", "MDSegmentedButtonItem") +__all__ = ( + "MDSegmentedButton", + "MDSegmentedButtonItem", + "MDSegmentButtonLabel", + "MDSegmentButtonIcon", +) import os from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder -from kivy.metrics import dp +from kivy.metrics import dp, sp from kivy.properties import ( BooleanProperty, ColorProperty, ListProperty, NumericProperty, StringProperty, - VariableListProperty, + OptionProperty, + ObjectProperty, ) from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.relativelayout import RelativeLayout +from kivymd.theming import ThemableBehavior from kivymd import uix_path -from kivymd.uix.behaviors import RectangularRippleBehavior, ScaleBehavior +from kivymd.uix.behaviors import ( + RectangularRippleBehavior, + DeclarativeBehavior, + BackgroundColorBehavior, +) +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.floatlayout import MDFloatLayout -from kivymd.uix.label import MDIcon +from kivymd.uix.label import MDIcon, MDLabel with open( os.path.join(uix_path, "segmentedbutton", "segmentedbutton.kv"), encoding="utf-8", ) as kv_file: - Builder.load_string(kv_file.read()) + Builder.load_string(kv_file.read(), filename="MDSegmentedButton") class MDSegmentedButtonItem( - RectangularRippleBehavior, ButtonBehavior, MDFloatLayout + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + RectangularRippleBehavior, + ButtonBehavior, + StateLayerBehavior, + RelativeLayout, ): """ Segment button item. - For more information, see in the - :class:`~kivymd.uix.behaviors.RectangularRippleBehavior` and + For more information see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.RectangularRippleBehavior` and :class:`~kivy.uix.behaviors.ButtonBehavior` and - :class:`~kivymd.uix.boxlayout.MDBoxLayout` + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and + :class:`~kivy.uix.relativelayout.RelativeLayout` and class documentation. """ - icon = StringProperty() + selected_color = ColorProperty(None) """ - Icon segment. + Color of the marked segment. - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. + .. versionadded:: 2.0.0 + + :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - text = StringProperty() + md_bg_color_disabled = ColorProperty(None) """ - Text segment. + The background color in (r, g, b, a) or string format of the list item when + the list item is disabled. - :attr:`text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ active = BooleanProperty(False) @@ -345,49 +474,100 @@ class MDSegmentedButtonItem( and defaults to `False`. """ - disabled_color = ColorProperty(None) - """ - Is active segment. + _icon = ObjectProperty() # MDSegmentButtonIcon object + _label = ObjectProperty() # MDSegmentButtonLabel object + _segmented_button = ObjectProperty() # MDSegmentedButton object + _line_color = ColorProperty(None) - :attr:`active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ + def add_widget(self, widget, *args, **kwargs): + def add_selected_icon(container: MDSegmentedButtonItemContainer): + selected_icon = MDSegmentButtonSelectedIcon( + _segmented_button=self._segmented_button, _item=self + ) + container.add_widget(selected_icon, index=1) - _no_ripple_effect = BooleanProperty(True) - _current_icon = "" - _current_md_bg_color = None + if isinstance( + widget, + (MDSegmentButtonLabel, MDSegmentButtonIcon), + ): + if isinstance(widget, MDSegmentButtonLabel): + self._label = widget + elif isinstance(widget, MDSegmentButtonIcon): + self._icon = widget + Clock.schedule_once( + lambda x: self._segmented_button._set_size_hint_min_x(widget) + ) + self.ids.container.add_widget(widget) + elif isinstance(widget, MDSegmentedButtonItemContainer): + Clock.schedule_once(lambda x: add_selected_icon(widget)) + return super().add_widget(widget) - def on_disabled(self, instance, value: bool) -> None: - def on_disabled(*args): - if value: - if not self._current_md_bg_color: - self._current_md_bg_color = self.md_bg_color - self.md_bg_color = ( - self.theme_cls.disabled_hint_text_color - if not self.disabled_color - else self.disabled_color + def on_line_color(self, instance, value) -> None: + """Fired when the values of :attr:`line_color` change.""" + + if not self.disabled and self.theme_line_color == "Custom": + self._line_color = value + + def on_active(self, instance, value) -> None: + """ + Fired when the :attr:`active` value changes. + Animates the marker icon for the element. + """ + + def set_active(*args): + t = ( + self._segmented_button.opening_icon_transition + if value + else self._segmented_button.hiding_icon_transition + ) + d = ( + self._segmented_button.opening_icon_duration + if value + else self._segmented_button.hiding_icon_duration + ) + + if self._icon and self._segmented_button: + if self._label: + Animation(font_size=0 if value else sp(18), t=t, d=d).start( + self._icon + ) + + selected_icon = self._get_selected_icon_from_container() + if selected_icon: + Animation(font_size=sp(18) if value else 0, t=t, d=d).start( + selected_icon ) - else: - if self._current_md_bg_color: - self.md_bg_color = self._current_md_bg_color - self._current_md_bg_color = None - Clock.schedule_once(on_disabled) + Clock.schedule_once(set_active, 0.5) - def on_icon(self, instance, icon_name: str): - if icon_name != "check": - self._current_icon = icon_name + def on_disabled(self, instance, value) -> None: + """Fired when the :attr:`disabled` value changes.""" + selected_icon = None -# TODO: -# Add the feature to use both text and icons in segments - -# https://m3.material.io/components/segmented-buttons/guidelines#26abac1c-c6bd-44c1-a969-8c910c880b98 -# Icons: optional check icon to indicate selected state - -# https://m3.material.io/components/segmented-buttons/overview#7b80f313-7d3a-4865-b26c-1f7ec98ba694 -# Hovered: add a color for the hovered segment - -# https://m3.material.io/components/segmented-buttons/specs#d730b3ba-c59e-4ef8-b652-20979fe20b67 -# Density: Each step down in density removes 4dp from the height - -# https://m3.material.io/components/segmented-buttons/specs#2d5cab36-1deb-40bd-9e37-bc2bb1657009 + if self._icon and self._segmented_button: + selected_icon = self._get_selected_icon_from_item() + elif not self._icon and self._segmented_button: + selected_icon = self._get_selected_icon_from_container() + + if selected_icon: + selected_icon.state_layer_color = self.theme_cls.transparentColor + + def _get_selected_icon_from_container(self): + selected_icon = None + for item in self.ids.container.children: + if isinstance(item, MDSegmentButtonSelectedIcon): + selected_icon = item + break + return selected_icon + + def _get_selected_icon_from_item(self): + selected_icon = None + for item in self.children: + if isinstance(item, MDSegmentButtonSelectedIcon): + selected_icon = item + break + return selected_icon class MDSegmentedButton(MDBoxLayout): @@ -396,20 +576,6 @@ class MDSegmentedButton(MDBoxLayout): For more information, see in the :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. - - :Events: - `on_marked` - The method is called when a segment is marked. - `on_unmarked` - The method is called when a segment is unmarked. - """ - - radius = VariableListProperty([20], length=4) - """ - Panel radius. - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[20, 20, 20, 20]`. """ multiselect = BooleanProperty(False) @@ -428,12 +594,12 @@ class MDSegmentedButton(MDBoxLayout): and defaults to `'linear'`. """ - hiding_icon_duration = NumericProperty(0.05) + hiding_icon_duration = NumericProperty(0.1) """ Duration of hiding the current icon. :attr:`hiding_icon_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.05`. + and defaults to `1`. """ opening_icon_transition = StringProperty("linear") @@ -444,210 +610,177 @@ class MDSegmentedButton(MDBoxLayout): and defaults to `'linear'`. """ - opening_icon_duration = NumericProperty(0.05) + opening_icon_duration = NumericProperty(0.1) """ The duration of opening a new icon of the "marked" type. :attr:`opening_icon_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.05`. + and defaults to `0.1`. """ - selected_items = ListProperty() + selected_segments = ListProperty() """ The list of :class:`~MDSegmentedButtonItem` objects that are currently marked. - :attr:`selected_items` is a :class:`~kivy.properties.ListProperty` + :attr:`selected_segments` is a :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ - selected_color = ColorProperty(None) + type = OptionProperty( + "large", options=["large", "normal", "medium", "small"] + ) """ - Color of the marked segment. + Density can be used in denser UIs where space is limited. + Density is only applied to the height. Each step down in density removes + '4dp' from the height. - :attr:`selected_color` is a :class:`~kivy.properties.ColorProperty` + .. versionadded:: 2.0.0 + + Available options are: 'large', 'normal', 'medium', 'small'. + + :attr:`type` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'large'`. + """ + + selected_icon_color = ColorProperty(None) + """ + Color in (r, g, b, a) or string format of the icon of the marked segment. + + .. versionadded:: 2.0.0 + + :attr:`selected_icon_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_event_type("on_marked") - self.register_event_type("on_unmarked") - Clock.schedule_once(self.mark_segment) - Clock.schedule_once(self.adjust_segment_radius) - Clock.schedule_once(self.adjust_segment_panel_width, 2) + def get_marked_items(self) -> list: + """Returns a list of active item objects.""" - def mark_segment(self, *args) -> None: - """Programmatically marks a segment.""" + return [item for item in self.ids.container.children if item.active] - for widget in self.children: - if widget.active: - widget.active = False - widget.dispatch("on_release") + def get_items(self) -> list: + """Returns a list of item objects.""" - if not self.multiselect: - break + return [item for item in self.ids.container.children] def adjust_segment_radius(self, *args) -> None: """Rounds off the first and last elements.""" - if self.children[0].radius == [0, 0, 0, 0]: - self.children[0].radius = (0, self.height / 2, self.height / 2, 0) - if self.children[-1].radius == [0, 0, 0, 0]: - self.children[-1].radius = (self.height / 2, 0, 0, self.height / 2) + _rad = self.height / 2 - def adjust_segment_panel_width(self, *args) -> None: - """ - Sets the width of all segments and the width of the panel - by the widest segment. - """ + _last_radius = [0, _rad, _rad, 0] + _first_radius = [_rad, 0, 0, _rad] + _optimal_radius = [0, 0, 0, 0] - if not self.size_hint_x: - width_list = [ - widget.ids.label_text.texture_size[0] - + (dp(72) if widget.icon else dp(48)) - for widget in self.children - ] - max_width = max(width_list) - self.width = max_width * len(width_list) - else: - max_width = self.width / len(self.children) + _child_count = len(self.ids.container.children) - for widget in self.children: - widget.width = max_width - - self.opacity = 1 - - for widget in self.children: - if widget.active: - widget.dispatch("on_release") - - def shift_segment_text(self, segment_item: MDSegmentedButtonItem) -> None: - """ - Shifts the segment text to the right, thus freeing up space - for the icon (when the segment is marked). - """ - - Animation( - x=( - segment_item.ids.label_text.x - + ( - dp(16) - if not segment_item.icon and not segment_item.active - else 0 - ) - ) - if not segment_item.active - else ( - segment_item.ids.label_text.x - - ( - dp(16) - if not segment_item.icon and segment_item.active - else 0 - ) - ), - d=0.2, - ).start(segment_item.ids.label_text) - - def show_icon_marked_segment( - self, segment_item: MDSegmentedButtonItem - ) -> None: - """ - Sets the icon for the marked segment and changes the icon scale - to the normal scale. - """ - - segment_item.ids.scale_icon.icon = "check" - if segment_item.ids.scale_icon.icon == "check" and segment_item.active: - segment_item.ids.scale_icon.icon = segment_item._current_icon - - Animation( - scale_value_x=1, - scale_value_y=1, - d=self.opening_icon_duration, - t=self.opening_icon_transition, - ).start(segment_item.ids.scale_icon) - - self.shift_segment_text(segment_item) - self.set_selected_segment_list(segment_item) - self.set_bg_marked_segment(segment_item) - - def hide_icon_marked_segment( - self, segment_item: MDSegmentedButtonItem - ) -> None: - """Changes the scale of the icon of the marked segment to zero.""" - - anim = Animation( - scale_value_x=0, - scale_value_y=0, - d=self.hiding_icon_duration, - t=self.hiding_icon_transition, - ) - anim.bind( - on_complete=lambda x, y: self.show_icon_marked_segment(segment_item) - ) - anim.start(segment_item.ids.scale_icon) - - def restore_bg_segment(self, segment_item) -> None: - Animation(md_bg_color=self.md_bg_color, d=0.2).start(segment_item) - - def set_bg_marked_segment(self, segment_item) -> None: - if segment_item.active: - Animation( - md_bg_color=self.selected_color - if self.selected_color - else self.theme_cls.primary_color, - d=0.2, - ).start(segment_item) - - def set_selected_segment_list(self, segment_item) -> None: - segment_item.active = not segment_item.active - - if segment_item.active: - self.selected_items.append(segment_item) - self.dispatch("on_marked", segment_item, segment_item.active) - else: - if segment_item in self.selected_items: - self.selected_items.remove(segment_item) - self.dispatch("on_unmarked", segment_item, segment_item.active) + for count, child in enumerate(self.ids.container.children): + if count == 0: + child.radius = _last_radius + elif count == _child_count - 1: + child.radius = _first_radius + else: + child.radius = _optimal_radius def mark_item(self, segment_item: MDSegmentedButtonItem) -> None: - if segment_item.active and not self.multiselect: - return - if not self.multiselect and self.selected_items: - self.uncheck_item() - else: - if segment_item.active: - self.restore_bg_segment(segment_item) + """Fired when a segment element is clicked (`on_release` event).""" - self.hide_icon_marked_segment(segment_item) + if not segment_item.disabled: + if not segment_item.active and not self.multiselect: + segment_item.active = True + elif self.multiselect: + segment_item.active = not segment_item.active - def uncheck_item(self) -> None: - for item in self.children: - if item.active: - self.hide_icon_marked_segment(item) - self.restore_bg_segment(item) - break + if not self.multiselect: + for widget in self.ids.container.children: + if segment_item is not widget: + widget.active = False def add_widget(self, widget, *args, **kwargs): if isinstance(widget, MDSegmentedButtonItem): + widget._segmented_button = self widget.bind(on_release=self.mark_item) + self.ids.container.add_widget(widget) + self.adjust_segment_radius() + elif isinstance(widget, MDSegmentedButtonContainer): return super().add_widget(widget) - def on_size(self, instance_segment_button, size: list) -> None: - """Called when the root screen is resized.""" + def remove_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDSegmentedButtonItem): + for child in widget.children[0].children: + if isinstance(child, MDSegmentButtonLabel) or isinstance( + child, MDSegmentButtonIcon + ): + self._set_size_hint_min_x(child, sign=-1) + self.ids.container.remove_widget(widget) + self.adjust_segment_radius() + elif isinstance(widget, MDSegmentedButtonContainer): + return super().remove_widget(widget) - if self.size_hint_x: - max_width = size[0] / len(self.children) - for widget in self.children: - widget.width = max_width - - def on_marked(self, *args): - """The method is called when a segment is marked.""" - - def on_unmarked(self, *args): - """The method is called when a segment is unmarked.""" + def _set_size_hint_min_x( + self, widget: MDSegmentButtonLabel | MDSegmentButtonIcon, sign: int = 1 + ): + self.ids.container.size_hint_min_x += sign * ( + widget.texture_size[0] + dp(36) + ) -class SegmentButtonIcon(MDIcon, ScaleBehavior): - """Implements an icon with scaling behavior.""" +class MDSegmentedButtonContainer(BoxLayout): + """ + Implements a container for placing :class:`~MDSegmentedButtonItem` + elements. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + +class MDSegmentedButtonItemContainer(BoxLayout): + """ + Implements a container for placing :class:`~MDSegmentButtonLabel` + and :class:`~MDSegmentButtonLabel` elements. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + +class MDSegmentButtonSelectedIcon(MDIcon): + """ + Implements the selected icon with scaling behavior + for :class:`~MDSegmentedButtonItem` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDIcon` class documentation. + """ + + _segmented_button = ObjectProperty() # MDSegmentedButton object + _item = ObjectProperty() # MDSegmentedButtonItem object + + +class MDSegmentButtonIcon(MDIcon): + """ + Implements a icon for :class:`~MDSegmentedButtonItem` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDIcon` class documentation. + """ + + +class MDSegmentButtonLabel(MDLabel): + """ + Implements a label for :class:`~MDSegmentedButtonItem` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__init__.py deleted file mode 100644 index c6517c5..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .segmentedcontrol import ( # NOQA F401 - MDSegmentedControl, - MDSegmentedControlItem, -) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 584777d..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__pycache__/segmentedcontrol.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__pycache__/segmentedcontrol.cpython-311.pyc deleted file mode 100644 index 9fe5e29..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/__pycache__/segmentedcontrol.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/segmentedcontrol.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/segmentedcontrol.kv deleted file mode 100644 index 8ceb004..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/segmentedcontrol.kv +++ /dev/null @@ -1,34 +0,0 @@ -#:import SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION kivymd.material_resources.SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION - - - - adaptive_height: True - halign: "center" - pos_hint: {"center_y": .5} - markup: True - - - - size_hint: None, None - size: segment_panel.size - - SegmentSwitch: - id: segment_switch - height: segment_panel.height - dp(12) - pos_hint: {"center_y": .5} - x: root._segment_switch_x - md_bg_color: root.segment_color - elevation: SEGMENT_CONTROL_SEGMENT_SWITCH_ELEVATION - _radius: root.radius[0] - 4 - shadow_radius: self._radius - width: - segment_panel.width / segment_panel.children_number \ - - segment_panel.spacing - - SegmentPanel: - id: segment_panel - radius: 12 - spacing: "12dp" - padding: "12dp" - size_hint: None, None - size: "320dp", root.segment_panel_height diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/segmentedcontrol.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/segmentedcontrol.py deleted file mode 100644 index 90db85c..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/segmentedcontrol/segmentedcontrol.py +++ /dev/null @@ -1,377 +0,0 @@ -""" -Components/SegmentedControl -=========================== - -.. versionadded:: 1.0.0 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/segmented-control-preview.jpg - :align: center - -Usage -===== - -.. tabs:: - - .. tab:: Declarative KV style - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - - KV = ''' - MDScreen: - - MDSegmentedControl: - pos_hint: {"center_x": .5, "center_y": .5} - - MDSegmentedControlItem: - text: "Male" - - MDSegmentedControlItem: - text: "Female" - - MDSegmentedControlItem: - text: "All" - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.screen import MDScreen - from kivymd.uix.segmentedcontrol import ( - MDSegmentedControl, MDSegmentedControlItem - ) - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDSegmentedControl( - MDSegmentedControlItem( - text="Male" - ), - MDSegmentedControlItem( - text="Female" - ), - MDSegmentedControlItem( - text="All" - ), - pos_hint={"center_x": 0.5, "center_y": 0.5} - ) - ) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-usage.gif - :align: center - -Events -====== - -.. code-block:: kv - - MDSegmentedControl: - on_active: app.on_active(*args) - -.. code-block:: python - - def on_active( - self, - segmented_control: MDSegmentedControl, - segmented_item: MDSegmentedControlItem, - ) -> None: - '''Called when the segment is activated.''' -""" - -__all__ = ("MDSegmentedControl", "MDSegmentedControlItem") - -import os - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - NumericProperty, - ObjectProperty, - StringProperty, - VariableListProperty, -) - -from kivymd import uix_path -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.card import MDSeparator -from kivymd.uix.label import MDLabel -from kivymd.uix.relativelayout import MDRelativeLayout - -with open( - os.path.join(uix_path, "segmentedcontrol", "segmentedcontrol.kv"), - encoding="utf-8", -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDSegmentedControlItem(MDLabel): - """ - Implements a label to place on the :class:`~SegmentPanel` panel. - - See :class:`~kivymd.uix.label.MDLabel` class documentation for more - information. - """ - - -# TODO: Add an attribute for the color of the active segment label. -class MDSegmentedControl(MDRelativeLayout): - """ - Implements a segmented control panel. - - For more information, see in the - :class:`~kivymd.uix.relativelayout.MDRelativeLayout` class documentation. - - :Events: - `on_active` - Called when the segment is activated. - """ - - md_bg_color = ColorProperty([0, 0, 0, 0]) - """ - Background color of the segment panel in (r, g, b, a) or string format. - - .. code-block:: kv - - MDSegmentedControl: - md_bg_color: "brown" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-md-bg-color.png - :align: center - - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - segment_color = ColorProperty([0, 0, 0, 0]) - """ - Color of the active segment in (r, g, b, a) or string format. - - .. code-block:: kv - - MDSegmentedControl: - md_bg_color: "brown" - segment_color: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-color.png - :align: center - - .. code-block:: kv - - MDSegmentedControl: - md_bg_color: "brown" - segment_color: "red" - - MDSegmentedControlItem: - text: "[color=fff]Male[/color]" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-text-color.png - :align: center - - :attr:`segment_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - segment_panel_height = NumericProperty("42dp") - """ - Height of the segment panel. - - .. code-block:: kv - - MDSegmentedControl: - segment_panel_height: "56dp" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-panel-height.png - :align: center - - :attr:`segment_panel_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'42dp'`. - """ - - separator_color = ColorProperty(None) - """ - The color of the separator between the segments in (r, g, b, a) or string - format. - - .. code-block:: kv - - MDSegmentedControl: - md_bg_color: "brown" - segment_color: "red" - separator_color: "white" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-separator-color.png - :align: center - - :attr:`separator_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - radius = VariableListProperty([16], length=4) - """ - Radius of the segment panel. - - .. code-block:: kv - - MDSegmentedControl: - radius: 0 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/md-segmented-control-segment-radius.png - :align: center - - :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[16, 16, 16, 16]`. - """ - - segment_switching_transition = StringProperty("in_cubic") - """ - Name of the animation type for the switch segment. - - :attr:`segment_switching_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'in_cubic'`. - """ - - segment_switching_duration = NumericProperty(0.2) - """ - Name of the animation type for the switch segment. - - :attr:`segment_switching_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - current_active_segment = ObjectProperty() - """ - The current active element of the :class:`~MDSegmentedControlItem` class. - - :attr:`current_active_segment` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - _segment_switch_x = NumericProperty(dp(4)) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_event_type("on_active") - - Clock.schedule_once(self.set_default_colors) - Clock.schedule_once(self._remove_last_separator) - - def set_default_colors(self, *args) -> None: - """ - Sets the colors of the panel and the switch if the colors are not set - by the user. - """ - - if self.md_bg_color == [0, 0, 0, 0]: - self.md_bg_color = self.theme_cls.bg_darkest - if self.segment_color == [0, 0, 0, 0]: - self.segment_color = self.theme_cls.bg_dark - - def animation_segment_switch(self, widget: MDSegmentedControlItem) -> None: - """Animates the movement of the switch.""" - - Animation( - _segment_switch_x=widget.x - dp(6), - t=self.segment_switching_transition, - d=self.segment_switching_duration, - ).start(self) - - def update_segment_panel_width( - self, widget: MDSegmentedControlItem - ) -> None: - """ - Sets the width of the panel for the elements of the - :class:`~MDSegmentedControlItem` class. - """ - - widget.text_size = (None, None) - widget.texture_update() - self.ids.segment_panel.width += ( - widget.texture_size[0] + self.ids.segment_panel.spacing - ) - - def update_separator_color(self, widget: MDSeparator) -> None: - """Updates the color of the separators between segments.""" - - widget.color = ( - self.separator_color - if self.separator_color - else self.theme_cls.divider_color - ) - - def add_widget(self, widget, *args, **kwargs): - if isinstance(widget, (SegmentPanel, SegmentSwitch)): - return super().add_widget(widget) - if isinstance(widget, MDSegmentedControlItem): - Clock.schedule_once( - lambda x: self.update_segment_panel_width(widget) - ) - widget.bind(on_touch_down=self.on_press_segment) - self.ids.segment_panel.add_widget(widget) - separator = MDSeparator(orientation="vertical") - self.ids.segment_panel.add_widget(separator) - if not self.ids.segment_panel._started: - self.ids.segment_panel._started = True - else: - self.ids.segment_panel.children_number += 1 - Clock.schedule_once( - lambda x: self.update_separator_color(separator) - ) - - def on_active(self, *args) -> None: - """Called when the segment is activated.""" - - def on_press_segment(self, widget: MDSegmentedControlItem, touch): - if widget.collide_point(touch.x, touch.y): - self.animation_segment_switch(widget) - self.current_active_segment = widget - self.dispatch("on_active", widget) - - def _remove_last_separator(self, *args): - self.ids.segment_panel.remove_widget(self.ids.segment_panel.children[0]) - - -class SegmentSwitch(MDRaisedButton): - """Implements a switch for the :class:`~MDSegmentedControl` class.""" - - _no_ripple_effect = BooleanProperty(True) - - -class SegmentPanel(MDBoxLayout): - """ - Implements a panel for placing items - :class:`~MDSegmentedControlItem` - for the :class:`~MDSegmentedControl` class. - """ - - children_number = NumericProperty(1) - - _started = BooleanProperty(defaultvalue=False) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__init__.py deleted file mode 100644 index 9be34e2..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .selection import MDSelectionList # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6aa4594..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__pycache__/selection.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__pycache__/selection.cpython-311.pyc deleted file mode 100644 index 21efa9f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/__pycache__/selection.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/selection.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/selection.kv deleted file mode 100644 index 7e3dead..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/selection.kv +++ /dev/null @@ -1,17 +0,0 @@ - - theme_text_color: "Custom" - text_color: self.icon_check_color - - canvas.before: - PushMatrix - Scale: - x: root.scale - y: root.scale - z: root.scale - origin: self.center - canvas.after: - PopMatrix - - - - md_bg_color: root.overlay_color if root.selected else (0, 0, 0, 0) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/selection.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/selection.py deleted file mode 100644 index 1ee8676..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selection/selection.py +++ /dev/null @@ -1,672 +0,0 @@ -""" -Components/Selection -==================== - -.. seealso:: - - `Material Design spec, Banner `_ - -.. rubric:: Selection refers to how users indicate specific items they intend to take action on. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-previous.png - :align: center - -Entering selection mode ------------------------ - -To select an item and enter selection mode, long press the item: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/enter-selection-mode.gif - :align: center - -Exiting selection mode ----------------------- - -To exit selection mode, tap each selected item until they’re all deselected: - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/exit-selection-mode.gif - :align: center - -Larger selections ------------------ - -.. note:: This feature is missing yet. - -Events ------- - -.. code-block:: python - - def on_selected(self, instance_selection_list, instance_selection_item): - '''Called when a list item is selected.''' - - def on_unselected(self, instance_selection_list, instance_selection_item): - '''Called when a list item is unselected.''' - -Example with TwoLineAvatarListItem ----------------------------------- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.utils import get_color_from_hex - - from kivymd.app import MDApp - from kivymd.uix.list import TwoLineAvatarListItem - - KV = ''' - - text: "Two-line item with avatar" - secondary_text: "Secondary text here" - _no_ripple_effect: True - - ImageLeftWidget: - source: "data/logo/kivy-icon-256.png" - - - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - id: toolbar - title: "Inbox" - left_action_items: [["menu"]] - right_action_items: [["magnify"], ["dots-vertical"]] - md_bg_color: 0, 0, 0, 1 - - MDBoxLayout: - padding: "24dp", "8dp", 0, "8dp" - adaptive_size: True - - MDLabel: - text: "Today" - adaptive_size: True - - ScrollView: - - MDSelectionList: - id: selection_list - spacing: "12dp" - overlay_color: app.overlay_color[:-1] + [.2] - icon_bg_color: app.overlay_color - on_selected: app.on_selected(*args) - on_unselected: app.on_unselected(*args) - on_selected_mode: app.set_selection_mode(*args) - ''' - - - class MyItem(TwoLineAvatarListItem): - pass - - - class Example(MDApp): - overlay_color = get_color_from_hex("#6042e4") - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(10): - self.root.ids.selection_list.add_widget(MyItem()) - - def set_selection_mode(self, instance_selection_list, mode): - if mode: - md_bg_color = self.overlay_color - left_action_items = [ - [ - "close", - lambda x: self.root.ids.selection_list.unselected_all(), - ] - ] - right_action_items = [["trash-can"], ["dots-vertical"]] - else: - md_bg_color = (0, 0, 0, 1) - left_action_items = [["menu"]] - right_action_items = [["magnify"], ["dots-vertical"]] - self.root.ids.toolbar.title = "Inbox" - - Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar) - self.root.ids.toolbar.left_action_items = left_action_items - self.root.ids.toolbar.right_action_items = right_action_items - - def on_selected(self, instance_selection_list, instance_selection_item): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - def on_unselected(self, instance_selection_list, instance_selection_item): - if instance_selection_list.get_selected_list_items(): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-example-with-listItem.gif - :align: center - -Example with FitImage ---------------------- - -.. code-block:: python - - from kivy.animation import Animation - from kivy.lang import Builder - from kivy.properties import ColorProperty - - from kivymd.app import MDApp - from kivymd.uix.fitimage import FitImage - - KV = ''' - MDBoxLayout: - orientation: "vertical" - md_bg_color: app.theme_cls.bg_light - - MDTopAppBar: - id: toolbar - title: "Inbox" - left_action_items: [["menu"]] - right_action_items: [["magnify"], ["dots-vertical"]] - md_bg_color: app.theme_cls.bg_light - specific_text_color: 0, 0, 0, 1 - - MDBoxLayout: - padding: "24dp", "8dp", 0, "8dp" - adaptive_size: True - - MDLabel: - text: "Today" - adaptive_size: True - - ScrollView: - - MDSelectionList: - id: selection_list - padding: "24dp", 0, "24dp", "24dp" - cols: 3 - spacing: "12dp" - overlay_color: app.overlay_color[:-1] + [.2] - icon_bg_color: app.overlay_color - progress_round_color: app.progress_round_color - on_selected: app.on_selected(*args) - on_unselected: app.on_unselected(*args) - on_selected_mode: app.set_selection_mode(*args) - ''' - - - class Example(MDApp): - overlay_color = ColorProperty("#6042e4") - progress_round_color = "#ef514b" - - def build(self): - return Builder.load_string(KV) - - def on_start(self): - for i in range(10): - self.root.ids.selection_list.add_widget( - FitImage( - source="image.png", - size_hint_y=None, - height="240dp", - ) - ) - - def set_selection_mode(self, instance_selection_list, mode): - if mode: - md_bg_color = self.overlay_color - left_action_items = [ - [ - "close", - lambda x: self.root.ids.selection_list.unselected_all(), - ] - ] - right_action_items = [["trash-can"], ["dots-vertical"]] - else: - md_bg_color = (1, 1, 1, 1) - left_action_items = [["menu"]] - right_action_items = [["magnify"], ["dots-vertical"]] - self.root.ids.toolbar.title = "Inbox" - - Animation(md_bg_color=md_bg_color, d=0.2).start(self.root.ids.toolbar) - self.root.ids.toolbar.left_action_items = left_action_items - self.root.ids.toolbar.right_action_items = right_action_items - - def on_selected(self, instance_selection_list, instance_selection_item): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - def on_unselected(self, instance_selection_list, instance_selection_item): - if instance_selection_list.get_selected_list_items(): - self.root.ids.toolbar.title = str( - len(instance_selection_list.get_selected_list_items()) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/selection-example-with-fitimage.gif - :align: center -""" - -__all__ = ("MDSelectionList",) - -import os -from typing import Union - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.graphics.context_instructions import Color -from kivy.graphics.vertex_instructions import ( - Ellipse, - RoundedRectangle, - SmoothLine, -) -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - StringProperty, -) - -from kivymd import uix_path -from kivymd.uix.behaviors import TouchBehavior -from kivymd.uix.button import MDIconButton -from kivymd.uix.list import MDList -from kivymd.uix.relativelayout import MDRelativeLayout - -with open( - os.path.join(uix_path, "selection", "selection.kv"), encoding="utf-8" -) as kv_file: - Builder.load_string(kv_file.read()) - - -class SelectionIconCheck(MDIconButton): - """Implements the icon for the checked item.""" - - scale = NumericProperty(0) - icon_check_color = ColorProperty([0, 0, 0, 1]) - - -class SelectionItem(MDRelativeLayout, TouchBehavior): - selected = BooleanProperty(False) - """ - Whether or not an item is checked. - - :attr:`selected` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - owner = ObjectProperty() - """ - Instance of :class:`~kivymd.uix.selection.MDSelectionList` class. - - :attr:`owner` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - instance_item = ObjectProperty() - """ - User item. Must be a Kivy or KivyMD widget. - - :attr:`instance_item` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - instance_icon = ObjectProperty() - """ - Instance of :class:`~kivymd.uix.selection.SelectionIconCheck` class. - - :attr:`instance_icon` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - overlay_color = ColorProperty([0, 0, 0, 0.2]) - """See :attr:`~MDSelectionList.overlay_color`.""" - - progress_round_size = NumericProperty(dp(46)) - """See :attr:`~MDSelectionList.progress_round_size`.""" - - progress_round_color = ColorProperty(None) - """See :attr:`~MDSelectionList.progress_round_color`.""" - - _progress_round = NumericProperty(0) - _progress_line_end = NumericProperty(0) - _progress_animation = BooleanProperty(False) - _touch_long = BooleanProperty(False) - _instance_progress_inner_circle_color = ObjectProperty() - _instance_progress_inner_circle_ellipse = ObjectProperty() - _instance_progress_inner_outer_color = ObjectProperty() - _instance_progress_inner_outer_line = ObjectProperty() - _instance_overlay_color = ObjectProperty() - _instance_overlay_rounded_rec = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_progress_round) - - def set_progress_round(self, interval: Union[int, float]) -> None: - with self.canvas.after: - self._instance_progress_inner_circle_color = Color( - rgba=(0, 0, 0, 0) - ) - self._instance_progress_inner_circle_ellipse = Ellipse( - size=self.get_progress_round_size(), - pos=self.get_progress_round_pos(), - ) - self.bind( - pos=self.update_progress_inner_circle_ellipse, - size=self.update_progress_inner_circle_ellipse, - ) - # FIXME: Radius value is not displayed. - self._instance_overlay_color = Color(rgba=(0, 0, 0, 0)) - self._instance_overlay_rounded_rec = RoundedRectangle( - size=self.size, - pos=self.pos, - radius=self.instance_item.radius - if hasattr(self.instance_item, "radius") - else [ - 0, - ], - ) - self.bind( - pos=self.update_overlay_rounded_rec, - size=self.update_overlay_rounded_rec, - ) - self._instance_progress_inner_outer_color = Color(rgba=(0, 0, 0, 0)) - self._instance_progress_inner_outer_line = SmoothLine( - width=dp(4), - circle=[ - self.center_x, - self.center_y, - self.progress_round_size * 0.58, - 0, - 0, - ], - ) - - def do_selected_item(self, *args) -> None: - Animation(scale=1, d=0.2).start(self.instance_icon) - self.selected = True - self._progress_animation = False - self._instance_overlay_color.rgba = self.get_overlay_color() - self.owner.dispatch("on_selected", self) - - def do_unselected_item(self) -> None: - Animation(scale=0, d=0.2).start(self.instance_icon) - self.selected = False - self._instance_overlay_color.rgba = self.get_overlay_color() - self.owner.dispatch("on_unselected", self) - - def do_animation_progress_line( - self, animation: Animation, instance_selection_item, value: float - ) -> None: - self._instance_progress_inner_outer_line.circle = ( - self.center_x, - self.center_y, - self.progress_round_size * 0.58, - 0, - 360 * value, - ) - - def update_overlay_rounded_rec(self, *args) -> None: - self._instance_overlay_rounded_rec.size = self.size - self._instance_overlay_rounded_rec.pos = self.pos - - def update_progress_inner_circle_ellipse(self, *args) -> None: - self._instance_progress_inner_circle_ellipse.size = ( - self.get_progress_round_size() - ) - self._instance_progress_inner_circle_ellipse.pos = ( - self.get_progress_round_pos() - ) - - def reset_progress_animation(self) -> None: - Animation.cancel_all(self) - self._progress_animation = False - self._instance_progress_inner_circle_color.rgba = (0, 0, 0, 0) - self._instance_progress_inner_outer_color.rgba = (0, 0, 0, 0) - self._instance_progress_inner_outer_line.circle = [ - self.center_x, - self.center_y, - self.progress_round_size * 0.58, - 0, - 0, - ] - self._progress_line_end = 0 - - def get_overlay_color(self) -> list: - return self.overlay_color if self.selected else (0, 0, 0, 0) - - def get_progress_round_pos(self) -> tuple: - return ( - (self.pos[0] + self.width / 2) - self.progress_round_size / 2, - self.center_y - self.progress_round_size / 2, - ) - - def get_progress_round_size(self) -> tuple: - return self.progress_round_size, self.progress_round_size - - def get_progress_round_color(self) -> tuple: - return ( - self.theme_cls.primary_color - if not self.progress_round_color - else self.progress_round_color - ) - - def get_progress_line_color(self) -> tuple: - return ( - self.theme_cls.primary_color[:-1] + [0.5] - if not self.progress_round_color - else self.progress_round_color[:-1] + [0.5] - ) - - def on_long_touch(self, *args) -> None: - if not self.owner.get_selected(): - self._touch_long = True - self._progress_animation = True - - def on_touch_up(self, touch): - if self.collide_point(*touch.pos): - if self._touch_long: - self._touch_long = False - return super().on_touch_up(touch) - - def on_touch_down(self, touch): - if self.collide_point(*touch.pos): - if self.selected: - self.do_unselected_item() - else: - if self.owner.selected_mode: - self.do_selected_item() - return super().on_touch_down(touch) - - def on__touch_long(self, instance_selection_tem, touch_value: bool) -> None: - if not touch_value: - self.reset_progress_animation() - - def on__progress_animation( - self, instance_selection_tem, touch_value: bool - ) -> None: - if touch_value: - anim = Animation(_progress_line_end=360, d=1, t="in_out_quad") - anim.bind( - on_progress=self.do_animation_progress_line, - on_complete=self.do_selected_item, - ) - anim.start(self) - self._instance_progress_inner_outer_color.rgba = ( - self.get_progress_line_color() - ) - self._instance_progress_inner_circle_color.rgba = ( - self.get_progress_round_color() - ) - else: - self.reset_progress_animation() - - -class MDSelectionList(MDList): - """ - Selection list class. - - For more information, see in the - :class:`~kivymd.uix.list.MDList` classes documentation. - - :Events: - `on_selected` - Called when a list item is selected. - `on_unselected` - Called when a list item is unselected. - """ - - selected_mode = BooleanProperty(False) - """ - List item selection mode. If `True` when clicking on a list item, it will - be selected. - - :attr:`selected_mode` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - icon = StringProperty("check") - """ - Name of the icon with which the selected list item will be marked. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'check'`. - """ - - icon_pos = ListProperty() - """ - The position of the icon that will mark the selected list item. - - :attr:`icon_pos` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - icon_bg_color = ColorProperty([1, 1, 1, 1]) - """ - Background color in (r, g, b, a) or string format of the icon that will - mark the selected list item. - - :attr:`icon_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - icon_check_color = ColorProperty([0, 0, 0, 1]) - """ - Color in (r, g, b, a) or string format of the icon that will mark the - selected list item. - - :attr:`icon_check_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - overlay_color = ColorProperty([0, 0, 0, 0.2]) - """ - The overlay color in (r, g, b, a) or string format of the selected list item. - - :attr:`overlay_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.2]]`. - """ - - progress_round_size = NumericProperty(dp(46)) - """ - Size of the spinner for switching of `selected_mode` mode. - - :attr:`progress_round_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(46)`. - """ - - progress_round_color = ColorProperty(None) - """ - Color in (r, g, b, a) or string format of the spinner for switching of - `selected_mode` mode. - - :attr:`progress_round_color` is an :class:`~kivy.properties.NumericProperty` - and defaults to `None`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.register_event_type("on_selected") - self.register_event_type("on_unselected") - - def add_widget(self, widget, index=0, canvas=None): - selection_icon = SelectionIconCheck( - icon=self.icon, - md_bg_color=self.icon_bg_color, - icon_check_color=self.icon_check_color, - ) - container = SelectionItem( - size_hint=(1, None), - height=widget.height, - instance_item=widget, - instance_icon=selection_icon, - overlay_color=self.overlay_color, - progress_round_size=self.progress_round_size, - progress_round_color=self.progress_round_color, - owner=self, - ) - container.add_widget(widget) - - if not self.icon_pos: - pos = ( - dp(12), - container.height / 2 - selection_icon.height / 2, - ) - else: - pos = self.icon_pos - selection_icon.pos = pos - container.add_widget(selection_icon) - return super().add_widget(container, index, canvas) - - def get_selected(self) -> bool: - """Returns ``True`` if at least one item in the list is checked.""" - - selected = False - for item in self.children: - if item.selected: - selected = True - break - return selected - - def get_selected_list_items(self) -> list: - """ - Returns a list of marked objects: - - [, ...] - """ - - selected_list_items = [] - for item in self.children: - if item.selected: - selected_list_items.append(item) - return selected_list_items - - def unselected_all(self) -> None: - for item in self.children: - item.do_unselected_item() - self.selected_mode = False - - def selected_all(self) -> None: - for item in self.children: - item.do_selected_item() - self.selected_mode = True - - def on_selected(self, *args): - """Called when a list item is selected.""" - - if not self.selected_mode: - self.selected_mode = True - - def on_unselected(self, *args): - """Called when a list item is unselected.""" - - self.selected_mode = self.get_selected() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/__init__.cpython-311.pyc index 1fe69ba..0ca516b 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/selectioncontrol.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/selectioncontrol.cpython-311.pyc index be4e0d2..d9ad1ad 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/selectioncontrol.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/__pycache__/selectioncontrol.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.kv index 3d3b015..e2d03d8 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.kv @@ -1,12 +1,26 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import colors kivymd.color_definitions.colors - - + _current_color: + ( \ + ( \ + self.theme_cls.primaryColor \ + if not self.color_active else self.color_active \ + ) \ + if self.active else \ + ( \ + self.theme_cls.onSurfaceVariantColor \ + if not self.color_inactive else self.color_inactive \ + ) \ + ) \ + if not self.disabled else \ + ( \ + self.theme_cls.onSurfaceColor[:-1] \ + + [self.checkbox_opacity_value_disabled_container] \ + if not self.color_disabled else self.color_disabled \ + ) + canvas: - Clear Color: - rgba: self.color + rgba: self._current_color Rectangle: texture: self.texture size: self.texture_size @@ -14,9 +28,22 @@ int(self.center_x - self.texture_size[0] / 2.), \ int(self.center_y - self.texture_size[1] / 2.) - color: self._current_color + # FIXME: Move to `kivymd/uix/behaviors/state_layer_behavior.py` + canvas.after: + # Clear + Color + rgba: self.state_layer_color + RoundedRectangle: + size: self.width + dp(20), self.height + dp(20) + pos: self.x - self.width / 2 + dp(2), self.y - self.height / 2 + dp(2) + radius: [(self.width + dp(20)) / 2, ] + + theme_text_color: "Custom" + text_color: self._current_color halign: "center" valign: "middle" + size_hint: None, None + size: "22dp", "22dp" @@ -26,115 +53,106 @@ ThumbIcon: id: icon font_size: "16sp" - theme_text_color: "Custom" pos_hint: {"center_x": .5, "center_y": .5} + theme_text_color: "Custom" + text_color: + ( \ + ( \ + ( \ + root.parent.icon_active_color \ + if root.parent.icon_active_color \ + else self.theme_cls.onPrimaryContainerColor \ + ) \ + if root.parent.icon_active and root.parent.active else \ + ( \ + root.parent.icon_inactive_color \ + if root.parent.icon_inactive_color \ + else self.theme_cls.surfaceContainerHighestColor \ + ) \ + ) \ + if not root.parent.disabled else \ + ( \ + self.theme_cls.onSurfaceColor[:-1] \ + + [root.parent.switch_opacity_value_disabled_icon] \ + if root.parent.icon_active else \ + self.theme_cls.surfaceContainerHighestColor[:-1] \ + + [root.parent.switch_opacity_value_disabled_icon] \ + ) \ + ) \ + if root.parent else self.theme_cls.transparentColor - canvas.before: - Color: - rgba: - ( \ - self.track_color_disabled \ - if self.track_color_disabled else \ - self.theme_cls.disabled_hint_text_color) \ - if self.disabled else \ - ( \ - ( \ - self.track_color_active \ - if self.track_color_active else \ - self.theme_cls.primary_color[:-1] + [.5]) \ - if self.active else \ - (self.track_color_inactive \ - if self.track_color_inactive else \ - self.theme_cls.disabled_hint_text_color) \ - ) - RoundedRectangle: - size: - (self.width + dp(14), dp(28)) \ - if root.widget_style == "ios" else \ - ( \ - (self.width - dp(8), dp(16)) \ - if app.theme_cls.material_style == "M2" else \ - (self.width + dp(16), dp(32)) \ - ) - pos: - (self.x - dp(2), self.center_y - dp(14)) \ - if root.widget_style == "ios" else \ - (self.x + dp(8), self.center_y - dp(8)) - radius: - [dp(14)] \ - if root.widget_style == "ios" else \ - [dp(7) if app.theme_cls.material_style == "M2" else dp(16)] - Color: - rgba: - ( \ - self.theme_cls.disabled_hint_text_color \ - if not root.active else (0, 0, 0, 0) \ - ) \ - if root.widget_style == "ios" \ - or app.theme_cls.material_style == "M3" else \ - (0, 0, 0, 0) - SmoothLine: - width: - 1 \ - if root.widget_style == "ios" \ - or app.theme_cls.material_style == "M2" else \ - 1.4 - rounded_rectangle: - ( \ - self.x - dp(2), self.center_y - dp(14), self.width + dp(14), \ - dp(28), dp(14), dp(14), dp(14), dp(14), dp(28) \ - ) \ - if root.widget_style == "ios" else \ - ( \ - (1, 1, 1, 1, 1, 1, 1, 1, 1) \ - if app.theme_cls.material_style == "M2" else \ - ( \ - self.x + dp(8), self.center_y - dp(8), self.width + dp(16), \ - dp(32), dp(16), dp(16), dp(16), dp(16), dp(32) \ - ) - ) + size_hint: None, None + size: dp(52), dp(32) + radius: [self.height / 2, ] + md_bg_color: + ( \ + self.track_color_disabled \ + if self.track_color_disabled else \ + ( \ + self.theme_cls.onSurfaceColor[:-1] \ + + [self.switch_opacity_value_disabled_container] \ + if self.active else self.theme_cls.surfaceContainerHighestColor[:-1] \ + + [self.switch_opacity_value_disabled_container] \ + ) \ + ) \ + if self.disabled else \ + ( \ + ( \ + self.track_color_active \ + if self.track_color_active else \ + self.theme_cls.primaryColor \ + ) \ + if self.active else \ + (self.track_color_inactive \ + if self.track_color_inactive else \ + self.theme_cls.surfaceContainerHighestColor \ + ) \ + ) + line_color: + ( \ + ( \ + self.theme_cls.outlineColor if not self.active else self.md_bg_color + ) \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] \ + + [self.switch_opacity_value_disabled_line] \ + ) \ + if self.theme_line_color == "Primary" else \ + self._line_color if not self.disabled else \ + ( \ + self.line_color_disabled \ + if self.line_color_disabled else \ + self._line_color \ + ) Thumb: id: thumb size_hint: None, None size: dp(24), dp(24) - elevation: - (2.5 if root.active else 1) \ - if app.theme_cls.material_style != "M3" else \ - 0 - pos: - (root.pos[0] + root._thumb_pos[0], root.pos[1] + root._thumb_pos[1]) \ - if root.widget_style == "ios" \ - or app.theme_cls.material_style == "M2" else \ - ( \ - root.pos[0] + self.width + root._thumb_pos[0], \ - root.pos[1] + (root.height / 2 - self.height / 2) + root._thumb_pos[1] \ - ) - _no_ripple_effect: - True \ - if app.theme_cls.material_style == "M3" \ - and root.widget_style != "ios" else \ - False + _no_ripple_effect: not root.ripple_effect md_bg_color: ( \ root.thumb_color_disabled \ if root.thumb_color_disabled else \ - get_color_from_hex(colors["Gray"]["800"]) \ + ( \ + root.theme_cls.surfaceColor \ + if root.active else root.theme_cls.onSurfaceColor[:-1] \ + + [root.switch_thumb_opacity_value_disabled_container] \ + ) \ ) \ if root.disabled else \ ( \ (root.thumb_color_active \ if root.thumb_color_active else \ - root.theme_cls.primary_color \ + root.theme_cls.onPrimaryColor \ ) \ if root.active else \ ( \ root.thumb_color_inactive \ if root.thumb_color_inactive else \ - get_color_from_hex(colors["Gray"]["50"] \ - ) \ + self.theme_cls.outlineColor \ ) \ ) on_touch_down: @@ -143,3 +161,7 @@ on_touch_up: if self.collide_point(*args[1].pos) and not root.disabled: \ setattr(root, "active", not root.active) + pos: + root.pos[0] + (self.width / 2) + root._thumb_pos[0] \ + + dp(6 if root.icon_inactive else 0), \ + root.pos[1] + (root.height / 2 - self.height / 2) + root._thumb_pos[1] \ No newline at end of file diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.py index 6bcefe6..b50b85c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/selectioncontrol/selectioncontrol.py @@ -16,6 +16,7 @@ Components/SelectionControls - MDSwitch_ .. MDCheckbox: + MDCheckbox ---------- @@ -152,34 +153,24 @@ Example adaptive_height: True MDCheckbox: - size_hint: None, None - size: "48dp", "48dp" group: root.group MDLabel: text: root.text adaptive_height: True - theme_text_color: "Custom" - text_color: "#B2B6AE" + padding_x: "12dp" pos_hint: {"center_y": .5} MDBoxLayout: orientation: "vertical" - md_bg_color: "#141612" - - MDTopAppBar: - md_bg_color: "#21271F" - specific_text_color: "#B2B6AE" - elevation: 0 - title: "Meal options" - left_action_items: [["arrow-left", lambda x: x]] - anchor_title: "left" + md_bg_color: self.theme_cls.backgroundColor MDBoxLayout: orientation: "vertical" adaptive_height: True padding: "12dp", "36dp", 0, 0 + spacing: "12dp" CheckItem: text: "Recieve emails" @@ -189,6 +180,7 @@ Example orientation: "vertical" adaptive_height: True padding: "24dp", 0, 0, 0 + spacing: "12dp" CheckItem: text: "Daily" @@ -213,7 +205,6 @@ Example class Example(MDApp): def build(self): - self.theme_cls.theme_style = "Dark" self.theme_cls.primary_palette = "Teal" return Builder.load_string(KV) @@ -224,6 +215,7 @@ Example :align: center .. MDSwitch: + MDSwitch -------- @@ -270,7 +262,7 @@ import os from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder -from kivy.metrics import dp, sp +from kivy.metrics import dp from kivy.properties import ( BooleanProperty, ColorProperty, @@ -278,18 +270,12 @@ from kivy.properties import ( StringProperty, ) from kivy.uix.behaviors import ToggleButtonBehavior -from kivy.uix.floatlayout import FloatLayout from kivymd import uix_path -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - CircularRippleBehavior, - CommonElevationBehavior, - ScaleBehavior, -) +from kivymd.uix.behaviors import CircularRippleBehavior, ScaleBehavior +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.label import MDIcon -from kivymd.utils import asynckivy with open( os.path.join(uix_path, "selectioncontrol", "selectioncontrol.kv"), @@ -299,15 +285,20 @@ with open( class MDCheckbox( - CircularRippleBehavior, ScaleBehavior, ToggleButtonBehavior, MDIcon + CircularRippleBehavior, + ScaleBehavior, + ToggleButtonBehavior, + MDIcon, ): """ Checkbox class. For more information, see in the - :class:`~kivymd.uix.behaviors.CircularRippleBehavior` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and + :class:`~kivymd.uix.behaviors.ripple_behavior.CircularRippleBehavior` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and :class:`~kivy.uix.behaviors.ToggleButtonBehavior` and - :class:`~kivymd.uix.label.MDIcon` + :class:`~kivymd.uix.label.label.MDIcon` classes documentation. """ @@ -394,26 +385,30 @@ class MDCheckbox( and defaults to `None`. """ - disabled_color = ColorProperty(None) + color_disabled = ColorProperty(None) """ Color in (r, g, b, a) or string format when the checkbox is in the disabled state. - .. code-block:: kv + .. versionadded:: 2.0.0 + Use :attr:`color_disabled` instead. - MDCheckbox: - disabled_color: "lightgrey" - disabled: True - active: True - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/checkbox-disabled-color.png - :align: center - - :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty` + :attr:`color_disabled` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ # Deprecated property. + disabled_color = ColorProperty(None) + """ + Color in (r, g, b, a) or string format when the checkbox is in the disabled state. + + .. deprecated:: 2.0.0 + Use :attr:`color_disabled` instead. + + :attr:`disabled_color` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + selected_color = ColorProperty(None, deprecated=True) """ Color in (r, g, b, a) or string format when the checkbox is in the active state. @@ -446,10 +441,6 @@ class MDCheckbox( scale_value_x=1, scale_value_y=1, duration=0.1, t="out_quad" ) super().__init__(**kwargs) - self.color_active = self.theme_cls.primary_color - self.color_inactive = self.theme_cls.secondary_text_color - self.disabled_color = self.theme_cls.divider_color - self._current_color = self.color_inactive self.check_anim_out.bind( on_complete=lambda *x: self.check_anim_in.start(self) ) @@ -459,38 +450,12 @@ class MDCheckbox( radio_icon_normal=self.update_icon, radio_icon_down=self.update_icon, group=self.update_icon, - color_active=self.update_color, - color_inactive=self.update_color, - disabled_color=self.update_color, - disabled=self.update_color, - state=self.update_color, - ) - self.theme_cls.bind( - theme_style=self.update_primary_color, - primary_color=self.update_primary_color, ) self.update_icon() - self.update_color() - - def update_primary_color(self, instance, value) -> None: - """ - Called when the values of - :attr:`kivymd.theming.ThemableBehavior.theme_cls.theme_style` and - :attr:`kivymd.theming.ThemableBehavior.theme_cls.primary_color` - change. - """ - - if value in ("Dark", "Light"): - if not self.disabled: - self.color = self.theme_cls.primary_color - else: - self.color = self.disabled_color - else: - self.color_active = value def update_icon(self, *args) -> None: """ - Called when the values of + Fired when the values of :attr:`checkbox_icon_normal` and :attr:`checkbox_icon_down` and :attr:`radio_icon_normal` and @@ -513,26 +478,22 @@ class MDCheckbox( else self.checkbox_icon_normal ) - def update_color(self, *args) -> None: - """ - Called when the values of - :attr:`color_active` and - :attr:`color_inactive` and - :attr:`disabled_color` and - :attr:`disabled` and - :attr:`state` - change. - """ + def set_root_active(self) -> None: + root_checkbox = self.get_widgets("root") + if root_checkbox: + MDCheckbox.__allow_root_checkbox_active = False + root_checkbox[0].active = True in [ + child.active for child in self.get_widgets("child") + ] + MDCheckbox.__allow_root_checkbox_active = True - if self.disabled: - self._current_color = self.disabled_color - elif self.state == "down": - self._current_color = self.color_active - else: - self._current_color = self.color_inactive + def set_child_active(self, active: bool): + for child in self.get_widgets("child"): + child.active = active + MDCheckbox.__allow_child_checkboxes_active = True def on_state(self, *args) -> None: - """Called when the values of :attr:`state` change.""" + """Fired when the values of :attr:`state` change.""" if self.state == "down": self.check_anim_in.cancel(self) @@ -549,7 +510,7 @@ class MDCheckbox( self.active = False def on_active(self, *args) -> None: - """Called when the values of :attr:`active` change.""" + """Fired when the values of :attr:`active` change.""" self.state = "down" if self.active else "normal" @@ -563,20 +524,7 @@ class MDCheckbox( if MDCheckbox.__allow_child_checkboxes_active: self.set_root_active() - def set_root_active(self) -> None: - root_checkbox = self.get_widgets("root") - if root_checkbox: - MDCheckbox.__allow_root_checkbox_active = False - root_checkbox[0].active = True in [ - child.active for child in self.get_widgets("child") - ] - MDCheckbox.__allow_root_checkbox_active = True - - def set_child_active(self, active: bool): - for child in self.get_widgets("child"): - child.active = active - MDCheckbox.__allow_child_checkboxes_active = True - + # FIXME: https://github.com/kivymd/KivyMD/issues/1574 def on_touch_down(self, touch): if self.collide_point(touch.x, touch.y): if self.group and self.group == "root": @@ -597,7 +545,7 @@ class ThumbIcon(MDIcon): """ -class Thumb(CommonElevationBehavior, CircularRippleBehavior, MDFloatLayout): +class Thumb(CircularRippleBehavior, MDFloatLayout): """Implements a thumb for the :class:`~MDSwitch` widget.""" def _set_ellipse(self, instance, value): @@ -614,13 +562,34 @@ class Thumb(CommonElevationBehavior, CircularRippleBehavior, MDFloatLayout): ) -class MDSwitch(ThemableBehavior, FloatLayout): +class MDSwitch(StateLayerBehavior, MDFloatLayout): """ Switch class. For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.floatlayout.FloatLayout` classes documentation. + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and + :class:`~kivymd.uix.floatlayout.MDFloatLayout` + classes documentation. + """ + + md_bg_color_disabled = ColorProperty(None) + """ + The background color in (r, g, b, a) or string format of the switch when + the switch is disabled. + + :attr:`md_bg_color_disabled` is a :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + ripple_effect = BooleanProperty(True) + """ + Allows or does not allow the ripple effect when activating/deactivating + the switch. + + .. versionadded:: 2.0.0 + + :attr:`ripple_effect` is a :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. """ active = BooleanProperty(False) @@ -818,20 +787,27 @@ class MDSwitch(ThemableBehavior, FloatLayout): and default to `None`. """ + line_color_disabled = ColorProperty(None) + """ + The color of the outline in the disabled state + + .. versionadded:: 2.0.0 + + :attr:`line_color_disabled` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + _thumb_pos = ListProperty([0, 0]) + _line_color = ColorProperty(None) def __init__(self, **kwargs): super().__init__(**kwargs) self.bind(icon_active=self.set_icon, icon_inactive=self.set_icon) - self.size_hint = (None, None) - self.size = (dp(36), dp(48)) - Clock.schedule_once(self._check_style) - Clock.schedule_once(lambda x: self._update_thumb_pos(animation=False)) Clock.schedule_once(lambda x: self.on_active(self, self.active)) def set_icon(self, instance_switch, icon_value: str) -> None: """ - Called when the values of + Fired when the values of :attr:`icon_active` and :attr:`icon_inactive` change. """ @@ -841,43 +817,29 @@ class MDSwitch(ThemableBehavior, FloatLayout): Clock.schedule_once(set_icon, 0.2) + def on_line_color(self, instance, value) -> None: + """Fired when the values of :attr:`line_color` change.""" + + if not self.disabled: + self._line_color = value + def on_active(self, instance_switch, active_value: bool) -> None: - """Called when the values of :attr:`active` change.""" + """Fired when the values of :attr:`active` change.""" - if self.theme_cls.material_style == "M3" and self.widget_style != "ios": - size = ( - ( - (dp(16), dp(16)) - if not self.icon_inactive - else (dp(24), dp(24)) - ) - if not active_value - else (dp(24), dp(24)) - ) - icon = "blank" - color = (0, 0, 0, 0) + size = ( + ((dp(16), dp(16)) if not self.icon_inactive else (dp(24), dp(24))) + if not active_value + else (dp(24), dp(24)) + ) + icon = "blank" - if self.icon_active and active_value: - icon = self.icon_active - color = ( - self.icon_active_color - if self.icon_active_color - else self.theme_cls.text_color - ) - elif self.icon_inactive and not active_value: - icon = self.icon_inactive - color = ( - self.icon_inactive_color - if self.icon_inactive_color - else self.theme_cls.text_color - ) - - Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb) - Animation(color=color, t="out_quad", d=0.2).start( - self.ids.thumb.ids.icon - ) - self.set_icon(self, icon) + if self.icon_active and active_value: + icon = self.icon_active + elif self.icon_inactive and not active_value: + icon = self.icon_inactive + Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb) + self.set_icon(self, icon) self._update_thumb_pos() # FIXME: If you move the cursor from the switch during the @@ -885,63 +847,28 @@ class MDSwitch(ThemableBehavior, FloatLayout): # the previous size does not work. The following code fixes this. def on_thumb_down(self) -> None: """ - Called at the on_touch_down event of the :class:`~Thumb` object. + Fired at the on_touch_down event of the :class:`~Thumb` object. Indicates the state of the switch "on/off" by an animation of increasing the size of the thumb. """ - if self.widget_style != "ios" and self.theme_cls.material_style == "M3": - if self.active: - size = (dp(28), dp(28)) - pos = ( - self.ids.thumb.pos[0] - dp(2), - self.ids.thumb.pos[1] - dp(1.8), - ) - else: - size = (dp(26), dp(26)) - pos = ( - ( - self.ids.thumb.pos[0] - dp(5), - self.ids.thumb.pos[1] - dp(5), - ) - if not self.icon_inactive - else ( - self.ids.thumb.pos[0] + dp(1), - self.ids.thumb.pos[1] - dp(1), - ) - ) - Animation(size=size, pos=pos, t="out_quad", d=0.2).start( - self.ids.thumb - ) + if self.active: + size = (dp(28), dp(28)) + else: + size = (dp(24), dp(24)) + + Animation(size=size, t="out_quad", d=0.2).start(self.ids.thumb) def _update_thumb_pos(self, *args, animation=True): if self.active: _thumb_pos = ( - self.width - - ( - dp(14) - if self.widget_style == "ios" - or self.theme_cls.material_style == "M2" - else dp(28) - ), - self.height / 2 - - ( - dp(12) - if self.widget_style == "ios" - or self.theme_cls.material_style == "M2" - else dp(16) - ), + self.width - dp(46 if self.icon_inactive else 40), + self.height / 2 - dp(16), ) else: _thumb_pos = ( 0 if not self.icon_inactive else dp(-14), - self.height / 2 - - ( - dp(12) - if self.widget_style == "ios" - or self.theme_cls.material_style == "M2" - else dp(16) - ), + self.height / 2 - dp(16), ) Animation.cancel_all(self, "_thumb_pos") @@ -951,7 +878,3 @@ class MDSwitch(ThemableBehavior, FloatLayout): ) else: self._thumb_pos = _thumb_pos - - def _check_style(self, *args): - if self.widget_style == "ios" or self.theme_cls.material_style == "M2": - self.set_icon(self, "") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__init__.py index a4edb11..0d97a47 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__init__.py @@ -1 +1 @@ -from .slider import MDSlider # NOQA F401 +from .slider import MDSlider, MDSliderHandle, MDSliderValueLabel # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/__init__.cpython-311.pyc index 77bed4f..a898381 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/slider.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/slider.cpython-311.pyc index f0462ab..d203229 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/slider.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/__pycache__/slider.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.kv index 5332381..b804ed9 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.kv @@ -1,169 +1,218 @@ -#:import Thumb kivymd.uix.selectioncontrol.Thumb -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import colors kivymd.color_definitions.colors - - - - - canvas: Clear + # Inactive track. Color: rgba: ( \ - self.track_color_disabled \ - if self.track_color_disabled else \ - self.theme_cls.disabled_hint_text_color) \ - if self.disabled else \ - ( \ - ( \ - self.track_color_active \ - if self.track_color_active else \ - ( \ - get_color_from_hex(colors["Gray"]["400"]) \ - if app.theme_cls.theme_style == "Light" else \ - (1, 1, 1, .3) \ - ) \ - ) \ - if self.active else \ - ( \ - self.track_color_inactive \ - if self.track_color_inactive else \ - ( \ - self.theme_cls.disabled_hint_text_color \ - if app.theme_cls.theme_style == "Light" else \ - get_color_from_hex(colors["Gray"]["800"]) \ - ) \ - ) \ - ) - Rectangle: - size: - (self.width - self.padding * 2 - self._offset[0], dp(4)) if \ - self.orientation == "horizontal" \ - else (dp(4),self.height - self.padding*2 - self._offset[1]) - pos: - (self.x + self.padding + self._offset[0], self.center_y - dp(4)) \ - if self.orientation == "horizontal" else \ - (self.center_x - dp(4), self.y + self.padding + self._offset[1]) - - # If 0 draw circle - Color: - rgba: - (0, 0, 0, 0) if not self._is_off else \ - ( \ - self.track_color_disabled \ - if self.disabled and self.track_color_disabled else \ - ( \ - self.theme_cls.disabled_hint_text_color \ - if app.theme_cls.theme_style == "Light" else \ - get_color_from_hex(colors["Gray"]["800"]) \ - ) \ - ) - Line: - width: 2 - circle: - (self.x + self.padding + dp(3), self.center_y - dp(2), 8 \ - if self.active else 6 ) if self.orientation == "horizontal" \ - else (self.center_x - dp(2), self.y + self.padding + dp(3), 8 \ - if self.active else 6) - - Color: - rgba: - (0, 0, 0, 0) if self._is_off \ - else \ - ( \ - ( \ - self.color if self.color else \ - app.theme_cls.primary_color \ + self.theme_cls.surfaceVariantColor \ + if not self.track_inactive_color else \ + self.track_inactive_color \ ) \ if not self.disabled else \ - ( \ - self.track_color_disabled \ - if self.track_color_disabled else \ - ( \ - (0, 0, 0, .26) \ - if app.theme_cls.theme_style == "Light" else (1, 1, 1, .3) \ - ) \ - ) \ - ) - Rectangle: + self.theme_cls.onSurfaceColor[:-1] + [.12] + SmoothRectangle: size: - ((self.width - self.padding * 2) * self.value_normalized, sp(4)) \ - if root.orientation == "horizontal" else \ - (sp(4), (self.height - self.padding * 2) * self.value_normalized) - pos: - (self.x + self.padding, self.center_y - dp(4)) \ + ( \ + self.width - self.padding * 2, \ + self.track_inactive_width \ + ) \ if self.orientation == "horizontal" else \ - (self.center_x - dp(4), self.y + self.padding) + ( \ + self.track_inactive_width, \ + self.height - self.padding * 2 \ + ) + pos: + ( \ + self.x + self.padding, \ + self.center_y - self.track_inactive_width \ + ) \ + if self.orientation == "horizontal" else \ + ( \ + self.center_x - self.track_inactive_width, \ + self.y + self.padding \ + ) - Thumb: - id: thumb - size_hint: None, None - size: - (dp(12), dp(12)) if root.disabled else ((dp(24), dp(24)) \ - if root.active else \ - (dp(16), dp(16))) - pos: - (root.value_pos[0] - dp(8), root.center_y - thumb.height / 2 - dp(2)) \ - if root.orientation == "horizontal" \ - else (root.center_x - thumb.width / 2 - dp(2), \ - root.value_pos[1] - dp(8)) - md_bg_color: - (0, 0, 0, 0) if root._is_off else \ - ( \ - ( \ - root.thumb_color_disabled \ - if root.thumb_color_disabled else \ - get_color_from_hex(colors["Gray"]["800"]) \ - ) \ - if root.disabled else \ - ( \ - (root.thumb_color_active \ - if root.thumb_color_active else \ - root.theme_cls.primary_color \ - ) \ - if root.active else \ - ( \ - root.thumb_color_inactive \ - if root.thumb_color_inactive else \ - root.theme_cls.primary_color \ - ) \ - ) \ - ) - elevation: 0 if root._is_off else (3 if root.active else 1) + # Active track. + Color: + rgba: + ( \ + self.theme_cls.primaryColor \ + if not self.track_active_color else \ + self.track_active_color \ + ) \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + [.38] + SmoothRectangle: + size: + ( \ + (self.width - self.padding * 2) * self.value_normalized, \ + self.track_active_width \ + ) \ + if self.orientation == "horizontal" else \ + ( \ + self.track_active_width, \ + (self.height - self.padding * 2) * self.value_normalized \ + ) + pos: + (self.x + self.padding, self.center_y - self.track_active_width) \ + if self.orientation == "horizontal" else \ + (self.center_x - self.track_active_width, self.y + self.padding) - HintBoxContainer: - id: hint_box + # Points of the inactive track when a step is used. + Color: + rgba: + ( \ + self.theme_cls.onPrimaryColor \ + if not self.track_active_step_point_color else \ + self.track_active_step_point_color \ + ) \ + if not self.disabled else \ + self.theme_cls.onPrimaryColor[:-1] + [.38] + Point: + points: self._inactive_points + pointsize: self.step_point_size + + # Points of the active track when a step is used. + Color: + rgba: + ( \ + self.theme_cls.primaryColor \ + if not self.track_inactive_step_point_color else \ + self.track_inactive_step_point_color \ + ) \ + if not self.disabled else \ + self.theme_cls.onSurfaceColor[:-1] + [.38] + Point: + points: self._active_points + pointsize: self.step_point_size + + # Label container. + BoxLayout: + id: value_container size_hint: None, None - md_bg_color: root.hint_bg_color if root.hint_bg_color else [0, 0, 0, 0] - elevation: 1.5 - opacity: 1 if root.active else 0 - radius: root.hint_radius - padding: "6dp", "6dp", "6dp", "8dp" - shadow_color: - ([0, 0, 0, 0.6] if root.hint_bg_color else [0, 0, 0, 0]) \ - if root.active else \ - [0, 0, 0, 0] - size: - lbl_value.width + self.padding[0] * 2, \ - lbl_value.height + self.padding[0] + size: self.minimum_size pos: - (root.value_pos[0] - dp(9), root.center_y - hint_box.height / 2 + dp(30)) \ + handle_container.center_x - self.width / 2, \ + root._value_container_y + + # Handle container. + BoxLayout: + id: handle_container + size_hint: None, None + size: self.minimum_size + pos: + ( \ + root.value_pos[0] - dp(0), \ + root.center_y - self.height / 2 - root.track_active_width / 2 \ + ) \ if root.orientation == "horizontal" else \ - (root.center_x - hint_box.width / 2 + dp(30), root.value_pos[1] - dp(8)) + ( \ + root.center_x - self.width / 2 - root.track_active_width / 2, \ + root.value_pos[1] - root.track_active_width / 2 \ + ) - MDLabel: - id: lbl_value - font_style: "Caption" - halign: "center" - theme_text_color: "Custom" - -text_size: None, None - adaptive_size: True - pos_hint: {"center_x": .5, "center_y": .5} - text_color: - app.theme_cls.primary_color \ - if not root.hint_text_color else root.hint_text_color - text: - str(root.value) \ - if isinstance(root.step, float) else str(int(root.value)) + + + _state_layer: state_layer + size_hint: None, None + radius: self.radius + + canvas: + Color: + rgba: + ( \ + self.theme_cls.primaryColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color \ + ) \ + if not self.disabled else self.theme_cls.onSurfaceColor + SmoothRoundedRectangle: + radius: self.radius + size: self.size + pos: self.pos + + MDSliderHandleStateLayer: + id: state_layer + + canvas.before: + Color: + rgba: + ( \ + app.theme_cls.primaryColor \ + if not root.state_layer_color else \ + root.state_layer_color \ + )[:-1] + [0.38] + SmoothRoundedRectangle: + radius: [root.state_layer_size[0] / 2, ] + size: root.state_layer_size + pos: + root.x - root.state_layer_size[0] / 4, \ + root.y - root.state_layer_size[1] / 4 + + + + canvas: + Color: + rgba: app.theme_cls.primaryColor + SmoothRoundedRectangle: + radius: [self.size[0] / 2, ] + pos: self.pos + size: self.size + + SmoothTriangle: + points: + [ \ + self.x + 18, self.y - 6, \ + self.x + 32, self.y + 9, \ + self.x + 5, self.y + 8 \ + ] + + # Label texture. + Color: + group: "md-slider-label-value-color" + Rectangle: + group: "md-slider-label-value-rect" + texture: + self._slider._value_label.texture \ + if self._slider and self._slider._value_label else \ + None + pos: + ( \ + ( \ + # X. + self._slider.value_pos[0] \ + - (self._slider._value_label.texture_size[0] / 2) + dp(10), \ + # Y. + self._slider.center_y + dp(28) \ + ) \ + if self._slider.orientation == "horizontal" else \ + ( \ + # X. + self._slider.center_x \ + - (self._slider._value_label.texture_size[0] / 2) - dp(2), \ + # Y. + self._slider.value_pos[1] + \ + self._slider._value_label.texture_size[1] \ + + self._slider._handle.state_layer_size[1] / 2, \ + ) \ + ) \ + if self._slider and self._slider._value_label else (0, 0) + size: + self._slider._value_label.texture_size \ + if self._slider and self._slider._value_label else (0, 0) + + size_hint: None, None + size: + self._slider._value_label.size \ + if self._slider and self._slider._value_label else \ + (0, 0) + + + + font_style: "Label" + role: "medium" + size_hint: None, None + theme_text_color: "Custom" + text_color: self.theme_cls.onPrimaryColor + halign: "center" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.py index f0824ae..17629be 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/slider/slider.py @@ -4,29 +4,81 @@ Components/Slider .. seealso:: - `Material Design spec, Sliders `_ + `Material Design spec, Sliders `_ .. rubric:: Sliders allow users to make selections from a range of values. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider.png :align: center + +- Sliders should present the full range of choices that are available +- Two types: continuous and discrete +- The slider should immediately reflect any input made by a user + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliders-types.png + :align: center + +1. Continuous slider +2. Discrete slider + +Usage +----- + +.. code-block:: python + + MDSlider( + MDSliderHandle( + ), + MDSliderValueLabel( + ), + step=10, + value=50, + ) + +.. code-block:: kv + + MDSlider: + step: 10 + value: 50 + + MDSliderHandle: + + MDSliderValueLabel: + + +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slider-anatomy.png + :align: center """ -__all__ = ("MDSlider",) +__all__ = ("MDSlider", "MDSliderHandle", "MDSliderValueLabel") import os +from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( - BooleanProperty, - ColorProperty, ListProperty, VariableListProperty, + StringProperty, + NumericProperty, + ObjectProperty, + ColorProperty, ) from kivy.uix.slider import Slider +from kivy.uix.widget import Widget +from kivymd.uix.label import MDLabel +from kivymd.uix.behaviors import ( + ScaleBehavior, + DeclarativeBehavior, + BackgroundColorBehavior, +) +from kivymd.uix.behaviors.focus_behavior import FocusBehavior from kivymd import uix_path from kivymd.theming import ThemableBehavior @@ -36,282 +88,504 @@ with open( Builder.load_string(kv_file.read()) -class MDSlider(ThemableBehavior, Slider): +class MDSlider(DeclarativeBehavior, ThemableBehavior, Slider): """ - Class for creating a Slider widget. See in the - :class:`~kivy.uix.slider.Slider` class documentation. + Slider class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.slider.Slider` + classes documentation. """ - active = BooleanProperty(False) + track_active_width = NumericProperty(dp(4)) """ - If the slider is clicked. + Width of the active track. - :attr:`active` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. + .. versionadded:: 2.0.0 + + :attr:`track_active_width` is an :class:`~kivy.properties.NumericProperty` + and defaults to `dp(4)`. """ - color = ColorProperty(None) + track_inactive_width = NumericProperty(dp(4)) """ - Color slider in (r, g, b, a) or string format. + Width of the inactive track. - .. code-block:: kv + .. versionadded:: 2.0.0 - MDSlider - color: "red" + :attr:`track_inactive_width` is an :class:`~kivy.properties.NumericProperty` + and defaults to `dp(4)`. + """ - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-color.png - :align: center + step_point_size = NumericProperty(dp(1)) + """ + Step point size. - :attr:`color` is an :class:`~kivy.properties.ColorProperty` + .. versionadded:: 2.0.0 + + :attr:`step_point_size` is an :class:`~kivy.properties.NumericProperty` + and defaults to `dp(1)`. + """ + + track_active_color = ColorProperty(None) + """ + Color of the active track. + + .. versionadded:: 2.0.0 + + .. versionchanged:: 2.0.0 + + Rename from `track_color_active` to `track_active_color` + + :attr:`track_active_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - hint = BooleanProperty(True) + track_active_step_point_color = ColorProperty(None) """ - If True, then the current value is displayed above the slider. + Color of step points on active track. - .. code-block:: kv + .. versionadded:: 2.0.0 - MDSlider - hint: True - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint.png - :align: center - - :attr:`hint` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - hint_bg_color = ColorProperty(None) - """ - Hint rectangle color in (r, g, b, a) or string format. - - .. code-block:: kv - - MDSlider - hint: True - hint_bg_color: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-bg-color.png - :align: center - - :attr:`hint_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - hint_text_color = ColorProperty(None) - """ - Hint text color in in (r, g, b, a) or string format. - - .. code-block:: kv - - MDSlider - hint: True - hint_bg_color: "red" - hint_text_color: "white" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-text-color.png - :align: center - - :attr:`hint_text_color` is an :class:`~kivy.properties.ColorProperty` + :attr:`track_active_step_point_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - hint_radius = VariableListProperty([dp(4), dp(4), dp(4), dp(4)]) + track_inactive_step_point_color = ColorProperty(None) """ - Hint radius. + Color of step points on inactive track. - .. code-block:: kv + .. versionadded:: 2.0.0 - MDSlider - hint: True - hint_bg_color: "red" - hint_text_color: "white" - hint_radius: [6, 0, 6, 0] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-hint-radius.png - :align: center - - :attr:`hint_radius` is an :class:`~kivy.properties.VariableListProperty` - and defaults to `[dp(4), dp(4), dp(4), dp(4)]`. + :attr:`track_inactive_step_point_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - thumb_color_active = ColorProperty(None) + track_inactive_color = ColorProperty(None) """ - The color in (r, g, b, a) or string format of the thumb when the slider is active. + Color of the inactive track. - .. versionadded:: 1.0.0 + .. versionadded:: 2.0.0 - .. code-block:: kv + .. versionchanged:: 2.0.0 - MDSlider - thumb_color_active: "red" + Rename from `track_color_inactive` to `track_inactive_color` - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-active.png - :align: center - - :attr:`thumb_color_active` is an :class:`~kivy.properties.ColorProperty` - and default to `None`. + :attr:`track_active_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - thumb_color_inactive = ColorProperty(None) + value_container_show_anim_duration = NumericProperty(0.2) """ - The color in (r, g, b, a) or string format of the thumb when the slider is inactive. + Duration of the animation opening of the label value. - .. versionadded:: 1.0.0 + .. versionadded:: 2.0.0 - .. code-block:: kv - - MDSlider - thumb_color_inactive: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-inactive.png - :align: center - - :attr:`thumb_color_inactive` is an :class:`~kivy.properties.ColorProperty` - and default to `None`. + :attr:`value_container_show_anim_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. """ - thumb_color_disabled = ColorProperty(None) + value_container_hide_anim_duration = NumericProperty(0.2) """ - The color in (r, g, b, a) or string format of the thumb when the slider is - in the disabled state. + Duration of closing the animation of the label value. - .. versionadded:: 1.0.0 + .. versionadded:: 2.0.0 - .. code-block:: kv - - MDSlider - value: 55 - disabled: True - thumb_color_disabled: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-thumb-color-disabled.png - :align: center - - :attr:`thumb_color_disabled` is an :class:`~kivy.properties.ColorProperty` - and default to `None`. + :attr:`value_container_hide_anim_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. """ - track_color_active = ColorProperty(None) + value_container_show_anim_transition = StringProperty("out_circ") """ - The color in (r, g, b, a) or string format of the track when the slider is active. + The type of the opening animation of the label value. - .. versionadded:: 1.0.0 + .. versionadded:: 2.0.0 - .. code-block:: kv - - MDSlider - track_color_active: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-active.png - :align: center - - :attr:`track_color_active` is an :class:`~kivy.properties.ColorProperty` - and default to `None`. + :attr:`value_container_show_anim_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_circ'`. """ - track_color_inactive = ColorProperty(None) + value_container_hide_anim_transition = StringProperty("out_circ") """ - The color in (r, g, b, a) or string format of the track when the slider is inactive. + The type of the closing animation of the label value. - .. versionadded:: 1.0.0 + .. versionadded:: 2.0.0 - .. code-block:: kv - - MDSlider - track_color_inactive: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-inactive.png - :align: center - - :attr:`track_color_inactive` is an :class:`~kivy.properties.ColorProperty` - and default to `None`. + :attr:`value_container_hide_anim_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_circ'`. """ - track_color_disabled = ColorProperty(None) + handle_anim_transition = StringProperty("out_circ") """ - The color in (r, g, b, a) or string format of the track when the slider is - in the disabled state. + Handle animation type. - .. versionadded:: 1.0.0 + .. versionadded:: 2.0.0 - .. code-block:: kv - - MDSlider - disabled: True - track_color_disabled: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/slide-track-color-disabled.png - :align: center - - :attr:`track_color_disabled` is an :class:`~kivy.properties.ColorProperty` - and default to `None`. + :attr:`handle_anim_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_circ'`. """ - show_off = BooleanProperty(True) + handle_anim_duration = NumericProperty(0.2) """ - Show the `'off'` ring when set to minimum value. + Handle animation duration. - :attr:`show_off` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. + .. versionadded:: 2.0.0 + + :attr:`handle_anim_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. """ - _thumb_pos = ListProperty([0, 0]) - # Internal state of ring. - _is_off = BooleanProperty(False) - # Internal adjustment to reposition sliders for ring. - _offset = ListProperty((0, 0)) + _value_label_container_size = ListProperty([0, 0]) # value label texture + _value_label = ObjectProperty() # value label texture + _value_container = ObjectProperty() # MDSliderValueContainer object + _value_container_y = NumericProperty(0) # MDSliderValueContainer object + _handle = ObjectProperty() # MDSliderHandle object + # List of points displayed on the slider when using the `step` for th + # active/inactive tracks. + _active_points = ListProperty() + _inactive_points = ListProperty() - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_thumb_icon) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + Clock.schedule_once(self._update_state_layer_pos, 0.5) + Clock.schedule_once(self.on_size) - def set_thumb_icon(self, *args) -> None: - self.ids.thumb.ids.icon.icon = "blank" + def add_widget(self, widget, index=0, canvas=None): + def set_value_container_y(*args): + self._value_container_y = self.ids.handle_container.y - def on_hint(self, instance, value) -> None: - def on_hint(*args): - if not value: - self.remove_widget(self.ids.hint_box) + if isinstance(widget, MDSliderValueLabel): + self._value_label = widget + self._value_container = MDSliderValueContainer(_slider=self) + self.ids.value_container.add_widget(self._value_container) + Clock.schedule_once(set_value_container_y) + elif isinstance(widget, MDSliderHandle): + widget._slider = self + self._handle = widget + self.ids.handle_container.add_widget(widget) + else: + return super().add_widget(widget) - # Schedule using for declarative style. - # Otherwise get AttributeError exception. - Clock.schedule_once(on_hint) + def update_points(self, instance, step) -> None: + """Draws the step points on the slider.""" - def on_value_normalized(self, *args) -> None: - """ - When the ``value == min`` set it to `'off'` state and make slider - a ring. - """ + def update_points(*args): + y = ( + self.center_y + if self.orientation == "horizontal" + else self.center_x + ) - self.track_active_width / 2 + slider_length = ( + self.width if self.orientation == "horizontal" else self.height + ) - (self.padding * 2) + slider_max_value = int(self.max) + multiplier = slider_length / slider_max_value + active_track_width = ( + ( + self.width + if self.orientation == "horizontal" + else self.height + ) + - self.padding * 2 + ) * self.value_normalized - self._update_is_off() + for i in range(0, slider_max_value + 1, step): + x = i * multiplier - def on_show_off(self, *args) -> None: - self._update_is_off() + if x < active_track_width: + points = self._inactive_points + else: + points = self._active_points - def on__is_off(self, *args) -> None: - self._update_offset() + if self.orientation == "vertical": + points.append(y) - def on_active(self, *args) -> None: - self._update_offset() + points.append( + (self.x if self.orientation == "horizontal" else self.y) + + x + + self.padding + + ( + (self.ids.handle_container.width / 2) + if i != self.max and i + else 0 + ) + ) + if self.orientation == "horizontal": + points.append(y) + + Clock.schedule_once(update_points) + + def on_size(self, *args) -> None: + """Fired when the widget is resized.""" + + self._update_points() def on_touch_down(self, touch): - if super().on_touch_down(touch): - self.active = True + if self.disabled or not self.collide_point(*touch.pos): + return + if touch.is_mouse_scrolling: + if "down" in touch.button or "left" in touch.button: + if self.step: + self.value = min(self.max, self.value + self.step) + else: + self.value = min( + self.max, self.value + (self.max - self.min) / 20 + ) + if "up" in touch.button or "right" in touch.button: + if self.step: + self.value = max(self.min, self.value - self.step) + else: + self.value = max( + self.min, self.value - (self.max - self.min) / 20 + ) + elif self.sensitivity == "handle": + if self.children[0].collide_point(*touch.pos): + touch.grab(self) + else: + touch.grab(self) + Clock.schedule_once(self._update_state_layer_pos) + Animation(value_pos=touch.pos, d=0.2).start(self) + + return True + + def on_value_pos(self, *args) -> None: + """ + Fired when the `value_pos` value changes. + Sets a new value for the value label texture. + """ + + self._update_points() + + if self._value_label and self._value_container: + # FIXME: I do not know how else I can update the texture. + self._value_label.text = "" + self._value_label.text = f"{int(self.value)}" + self._value_label.texture_update() + label_value_rect = self._value_container.canvas.get_group( + "md-slider-label-value-rect" + )[0] + label_value_rect.texture = None + label_value_rect.texture = self._value_label.texture + label_value_rect.size = self._value_label.texture_size def on_touch_up(self, touch): - if super().on_touch_up(touch): - self.active = False + if touch.grab_current == self: + if self._handle: + self._handle.on_leave() + return True - def _update_offset(self): + def on_touch_move(self, touch): + if self.collide_point(touch.x, touch.y): + if self._handle: + self._update_state_layer_pos() + if self._handle and not self._handle._active: + self._handle.on_enter() + return super().on_touch_move(touch) + + def on_handle_enter(self) -> None: + """Scales the container of the label value.""" + + if self._handle and self._value_label: + Animation( + scale_value_x=1, + scale_value_y=1, + t=self.value_container_show_anim_transition, + d=self.value_container_show_anim_duration, + ).start(self._value_container) + Animation( + _value_container_y=self.ids.handle_container.y + dp(32), + t=self.value_container_show_anim_transition, + d=self.value_container_show_anim_duration, + ).start(self) + + def on_handle_leave(self) -> None: + """Scales the container of the label value.""" + + if self._handle and self._value_label: + Animation( + scale_value_x=0, + scale_value_y=0, + d=self.value_container_hide_anim_duration, + t=self.value_container_hide_anim_transition, + ).start(self._value_container) + Animation( + _value_container_y=self._value_container_y - dp(24), + t=self.value_container_hide_anim_transition, + d=self.value_container_hide_anim_duration, + ).start(self) + + def _update_points(self, *args) -> None: + if self.step: + self._active_points = [] + self._inactive_points = [] + self.update_points(self, self.step) + + def _update_state_layer_pos(self, *args): + if self._handle: + self._handle.ids.state_layer.scale_value_center = ( + self.ids.handle_container.center + ) + + +class MDSliderHandle( + ThemableBehavior, BackgroundColorBehavior, FocusBehavior, Widget +): + """ + Handle class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.focus_behavior.FocusBehavior` and + :class:`~kivy.uix.widget.Widget` + classes documentation. + """ + + radius = VariableListProperty([dp(10)], length=4) + """ + Handle radius. + + :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(10), dp(10), dp(10), dp(10)]`. + """ + + size = ListProperty([dp(20), dp(20)]) + """ + Handle size. + + :attr:`size` is an :class:`~kivy.properties.ListProperty` + and defaults to `[dp(20), dp(20)]`. + """ + + state_layer_size = ListProperty([dp(40), dp(40)]) + """ + Handle state layer size. + + :attr:`state_layer_size` is an :class:`~kivy.properties.ListProperty` + and defaults to `[dp(40), dp(40)]`. + """ + + state_layer_color = ColorProperty(None) + """ + Handle state layer color. + + :attr:`state_layer_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + _slider = ObjectProperty() # MDSlider object + _active = False # is the layer currently displayed + _state_layer = ObjectProperty() # MDSliderStateLayer object + + def on_enter(self) -> None: """ - Offset is used to shift the sliders so the background color - shows through the off circle. + Fired when mouse enter the bbox of the widget. + Animates the display of the slider handle layer. """ - d = 2 if self.active else 0 - self._offset = (dp(11 + d), dp(11 + d)) if self._is_off else (0, 0) + if self._slider: + if self._state_layer and not self._slider.disabled: + self._active = True + anim = Animation(scale_value_x=1, scale_value_y=1, d=0.2) + anim.bind(on_complete=self._slider._update_state_layer_pos) + anim.start(self._state_layer) + if not self._slider.disabled: + self._slider.on_handle_enter() - def _update_is_off(self): - self._is_off = self.show_off and (self.value_normalized == 0) + def on_leave(self) -> None: + """ + Fired when the mouse goes outside the widget border. + Animates the hiding of the slider handle layer. + """ + + if self._slider: + if self._state_layer and not self._slider.disabled: + self._active = False + anim = Animation(scale_value_x=0, scale_value_y=0, d=0.2) + anim.bind(on_complete=self._slider._update_state_layer_pos) + anim.start(self._state_layer) + if not self._slider.disabled: + self._slider.on_handle_leave() + + +class MDSliderHandleStateLayer(ScaleBehavior, Widget): + """ + Slider state layer class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivy.uix.widget.Widget` + classes documentation. + """ + + scale_value_x = NumericProperty(0) + """ + X-axis value. + + :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ + + scale_value_y = NumericProperty(0) + """ + Y-axis value. + + :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ + + +class MDSliderValueLabel(MDLabel): + """ + Implements the value label. + + For more information, see in the :class:`~kivymd.uix.label.label.MDLabel` + class documentation. + + .. versionadded:: 2.0.0 + """ + + size = ListProperty([dp(36), dp(36)]) + """ + Container size for the label value. + + :attr:`handle_anim_transition` is an :class:`~kivy.properties.ListProperty` + and defaults to `[dp(36), dp(36)]`. + """ + + +class MDSliderValueContainer(ScaleBehavior, Widget): + """ + Implements the container for value label. + + For more information, see in the + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivy.uix.widget.Widget` + classes documentation. + + .. versionadded:: 2.0.0 + """ + + scale_value_x = NumericProperty(0) + """ + X-axis value. + + :attr:`scale_value_x` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ + + scale_value_y = NumericProperty(0) + """ + Y-axis value. + + :attr:`scale_value_y` is an :class:`~kivy.properties.NumericProperty` + and defaults to `1`. + """ + + _slider = ObjectProperty() # MDSlider object diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/__init__.cpython-311.pyc index e0a77ba..27e1516 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/sliverappbar.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/sliverappbar.cpython-311.pyc index d19ca23..131946f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/sliverappbar.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/__pycache__/sliverappbar.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.kv index 6757d18..beb9bb6 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.kv @@ -1,5 +1,4 @@ #:import ScrollEffect kivy.effects.scroll.ScrollEffect -#:import get_color_from_hex kivy.utils.get_color_from_hex @@ -13,7 +12,7 @@ rgba: root.background_color \ if root.background_color else \ - root.theme_cls.primary_color + root.theme_cls.primaryColor a: root._opacity Rectangle: pos: self.pos @@ -31,11 +30,20 @@ on_scroll_start: if not root._scroll_was_moving: root._scroll_was_moving = True - MDBoxLayout: + BoxLayout: id: scroll_box - adaptive_height: True orientation: "vertical" + size_hint_y: None + height: self.minimum_height BoxLayout: size_hint_y: None height: root.max_height + + + + adaptive_height: True + md_bg_color: + self.theme_cls.surfaceColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.py index f8e8ab5..a16e59f 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/sliverappbar/sliverappbar.py @@ -6,7 +6,10 @@ Components/SliverAppbar .. rubric:: MDSliverAppbar is a Material Design widget in KivyMD which gives scrollable or collapsible - `MDTopAppBar `_ + `MDTopAppBar `_ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-appbar-preview.gif + :align: center .. note:: This widget is a modification of the `silverappbar.py `_ module. @@ -20,15 +23,21 @@ Usage MDSliverAppbar: + MDTopAppBar: + [...] + MDSliverAppbarHeader: # Custom content. - ... + [...] # Custom list. MDSliverAppbarContent: -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-usage.png +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-appbar-anatomy.png :align: center Example @@ -39,84 +48,139 @@ Example from kivy.lang.builder import Builder from kivymd.app import MDApp - from kivymd.uix.card import MDCard + from kivymd.uix.list import MDListItem KV = ''' - - size_hint_y: None - height: "86dp" - padding: "4dp" - radius: 12 + + theme_bg_color: "Custom" + md_bg_color: "2d4a50" - FitImage: - source: "avatar.jpg" - radius: root.radius - size_hint_x: None - width: root.height + MDListItemLeadingAvatar + source: "avatar.png" - MDBoxLayout: - orientation: "vertical" - adaptive_height: True - spacing: "6dp" - padding: "12dp", 0, 0, 0 - pos_hint: {"center_y": .5} + MDListItemHeadlineText: + text: "Ibanez" - MDLabel: - text: "Title text" - font_style: "H5" - bold: True - adaptive_height: True + MDListItemSupportingText: + text: "GRG121DX-BKF" - MDLabel: - text: "Subtitle text" - theme_text_color: "Hint" - adaptive_height: True + MDListItemTertiaryText: + text: "$445,99" + + MDListItemTrailingIcon: + icon: "guitar-electric" MDScreen: MDSliverAppbar: background_color: "2d4a50" + hide_appbar: True + + MDTopAppBar: + type: "medium" + + MDTopAppBarLeadingButtonContainer: + + MDActionTopAppBarButton: + icon: "arrow-left" + + MDTopAppBarTitle: + text: "Sliver toolbar" + + MDTopAppBarTrailingButtonContainer: + + MDActionTopAppBarButton: + icon: "attachment" + + MDActionTopAppBarButton: + icon: "calendar" + + MDActionTopAppBarButton: + icon: "dots-vertical" MDSliverAppbarHeader: - MDRelativeLayout: - - FitImage: - source: "bg.jpg" + FitImage: + source: "bg.jpg" MDSliverAppbarContent: id: content orientation: "vertical" padding: "12dp" - spacing: "12dp" - adaptive_height: True + theme_bg_color: "Custom" + md_bg_color: "2d4a50" ''' - class CardItem(MDCard): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.elevation = 1 + class GuitarItem(MDListItem): + ... class Example(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" return Builder.load_string(KV) def on_start(self): for x in range(10): - self.root.ids.content.add_widget(CardItem()) + self.root.ids.content.add_widget(GuitarItem()) Example().run() + +API break +========= + +1.2.0 version +------------- + +.. code-block:: kv + + #:import SliverToolbar __main__.SliverToolbar + + Root: + + MDSliverAppbar: + [...] + + MDSliverAppbarHeader: + + [...] + + MDSliverAppbarContent: + [...] + +.. code-block:: python + + class SliverToolbar(MDTopAppBar): + [...] + +2.0.0 version +------------- + +.. code-block:: kv + + Root: + + MDSliverAppbar: + [...] + + MDTopAppBar: + [...] + + MDSliverAppbarHeader: + + [...] + + MDSliverAppbarContent: + [...] """ __all__ = ("MDSliverAppbar", "MDSliverAppbarHeader", "MDSliverAppbarContent") import os -from typing import Union from kivy.clock import Clock from kivy.core.window import Window @@ -125,13 +189,15 @@ from kivy.properties import ( BooleanProperty, ColorProperty, NumericProperty, - ObjectProperty, VariableListProperty, + ObjectProperty, ) +from kivy.uix.boxlayout import BoxLayout from kivymd import uix_path +from kivymd.theming import ThemableBehavior from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.toolbar import MDTopAppBar +from kivymd.uix.appbar import MDTopAppBar with open( os.path.join(uix_path, "sliverappbar", "sliverappbar.kv"), encoding="utf-8" @@ -151,167 +217,33 @@ class MDSliverAppbarContent(MDBoxLayout): :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. """ - md_bg_color = ColorProperty([0, 0, 0, 0]) - """ - See :attr:`~kivymd.uix.sliverappbar.sliverappbar.MDSliverAppbar.background_color`. - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Clock.schedule_once(self.set_bg_color) - - def set_bg_color(self, interval: Union[int, float]) -> None: - if self.md_bg_color == [0, 0, 0, 0]: - self.md_bg_color = self.theme_cls.bg_normal - - -class MDSliverAppbarHeader(MDBoxLayout): +class MDSliverAppbarHeader(BoxLayout): """ Sliver app bar header class. For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. """ -class MDSliverAppbar(MDBoxLayout): +class MDSliverAppbar(ThemableBehavior, BoxLayout): """ - Sliver app bar class. + Sliver appbar class. For more information, see in the - :class:`~kivymd.uix.boxlayout.MDBoxLayout` class documentation. + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. :Events: :attr:`on_scroll_content` - Called when the list of custom content is being scrolled. - """ - - toolbar_cls = ObjectProperty() - """ - Must be an object of the :class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar' class. - See :class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar` class documentation - for more information. - - By default, MDSliverAppbar widget uses the MDTopAppBar class with no - parameters. - - .. code-block:: python - - from kivy.lang.builder import Builder - - from kivymd.uix.card import MDCard - from kivymd.uix.toolbar import MDTopAppBar - - KV = ''' - #:import SliverToolbar __main__.SliverToolbar - - - - size_hint_y: None - height: "86dp" - padding: "4dp" - radius: 12 - - FitImage: - source: "avatar.jpg" - radius: root.radius - size_hint_x: None - width: root.height - - MDBoxLayout: - orientation: "vertical" - adaptive_height: True - spacing: "6dp" - padding: "12dp", 0, 0, 0 - pos_hint: {"center_y": .5} - - MDLabel: - text: "Title text" - font_style: "H5" - bold: True - adaptive_height: True - - MDLabel: - text: "Subtitle text" - theme_text_color: "Hint" - adaptive_height: True - - - MDScreen: - - MDSliverAppbar: - background_color: "2d4a50" - toolbar_cls: SliverToolbar() - - MDSliverAppbarHeader: - - MDRelativeLayout: - - FitImage: - source: "bg.jpg" - - MDSliverAppbarContent: - id: content - orientation: "vertical" - padding: "12dp" - spacing: "12dp" - adaptive_height: True - ''' - - - class CardItem(MDCard): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.elevation = 1 - - - class SliverToolbar(MDTopAppBar): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.shadow_color = (0, 0, 0, 0) - self.type_height = "medium" - self.headline_text = "Headline medium" - self.left_action_items = [["arrow-left", lambda x: x]] - self.right_action_items = [ - ["attachment", lambda x: x], - ["calendar", lambda x: x], - ["dots-vertical", lambda x: x], - ] - - - class Example(MDApp): - def build(self): - self.theme_cls.material_style = "M3" - return Builder.load_string(KV) - - def on_start(self): - for x in range(10): - self.root.ids.content.add_widget(CardItem()) - - - Example().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-toolbar-cls.gif - :align: center - - :attr:`toolbar_cls` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. + Fired when the list of custom content is being scrolled. """ background_color = ColorProperty(None) """ - Background color of toolbar in (r, g, b, a) or string format. - - .. code-block:: kv - - MDSliverAppbar: - background_color: "2d4a50" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-background-color.png - :align: center + Background color of appbar in (r, g, b, a) or string format. :attr:`background_color` is an :class:`~kivy.properties.ColorProperty` and defaults to `None`. @@ -326,34 +258,40 @@ class MDSliverAppbar(MDBoxLayout): MDSliverAppbar: max_height: "200dp" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-max-height.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-appbar-max-height.png :align: center :attr:`max_height` is an :class:`~kivy.properties.NumericProperty` and defaults to `Window.height / 2`. """ - hide_toolbar = BooleanProperty(True) + hide_appbar = BooleanProperty(None) """ - Whether to hide the toolbar when scrolling through a list + Whether to hide the appbar when scrolling through a list of custom content. + .. versionchanged:: 2.0.0 + + Rename `hide_toolbar` to `hide_appbar` attribute. + .. code-block:: kv MDSliverAppbar: - hide_toolbar: False + hide_appbar: False - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-hide-toolbar.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-appbar-hide-appbar-false.gif :align: center + .. code-block:: kv + MDSliverAppbar: - hide_toolbar: True + hide_appbar: True - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-hide-toolbar-true.gif + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-appbar-hide-appbar-true.gif :align: center - :attr:`hide_toolbar` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. + :attr:`hide_appbar` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `None`. """ radius = VariableListProperty([20], length=4) @@ -395,72 +333,51 @@ class MDSliverAppbar(MDBoxLayout): _opacity = NumericProperty() _scroll_was_moving = BooleanProperty(False) _last_scroll_y_pos = 0.0 + _appbar = ObjectProperty() - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.register_event_type("on_scroll_content") + def on_hide_appbar(self, instance, value) -> None: + """Fired when the `hide_appbar` value changes.""" + + if not value: + self.background_color = self.theme_cls.transparentColor + def on_scroll_content( self, - instance_sliverappbar: object = None, + instance: object = None, value: float = 1.0, direction: str = "up", ): """ - Called when the list of custom content is being scrolled. + Fired when the list of custom content is being scrolled. - :param instance_sliverappbar: :class:`~MDSliverAppbar` + :param instance: :class:`~MDSliverAppbar` :param value: see :attr:`~kivy.uix.scrollview.ScrollView.scroll_y` :param direction: scroll direction: 'up/down' """ - def on_background_color( - self, instance_sliver_appbar, color_value: list - ) -> None: - if self.toolbar_cls: - self.toolbar_cls.md_bg_color = color_value + def on_background_color(self, instance, color) -> None: + """Fired when the `background_color` value changes.""" - def on_toolbar_cls( - self, instance_sliver_appbar, instance_toolbar_cls: MDTopAppBar - ) -> None: - """Called when a value is set to the :attr:`toolbar_cls` parameter.""" - - def on_toolbar_cls(*args): - # If an MDTopAppBar object is already in use, delete it - # before adding a new MDTopAppBar object. - for widget in self.ids.float_box.children: - if issubclass(widget.__class__, MDTopAppBar): - self.ids.float_box.remove_widget(widget) - - # Adding a custom MDTopAppBar object. - if issubclass(instance_toolbar_cls.__class__, MDTopAppBar): - instance_toolbar_cls.pos_hint = {"top": 1} - instance_toolbar_cls.elevation = 0 - self.ids.float_box.add_widget(instance_toolbar_cls) - else: - raise MDSliverAppbarException( - "The `toolbar_cls` parameter must be an object of the " - "`kivymd.uix.toolbar.MDTopAppBar class`" - ) - - # Schedule using for declarative style. - # Otherwise get AttributeError exception. - Clock.schedule_once(on_toolbar_cls) + if self._appbar: + self._appbar.canvas.get_group("md-top-app-bar-color")[ + 0 + ].rgba = color def on_vbar(self) -> None: if not self.background_color: - self.background_color = self.theme_cls.primary_color - - if not self.toolbar_cls: - self.toolbar_cls = self.get_default_toolbar() + self.background_color = self.theme_cls.primaryColor scroll_box = self.ids.scroll_box vbar = self.ids.scroll.vbar - toolbar_percent = (self.toolbar_cls.height / scroll_box.height) * 100 + appbar_percent = (self._appbar.height / scroll_box.height) * 100 current_percent = (vbar[0] + vbar[1]) * 100 percent_min = ( 1 - self.max_height / scroll_box.height - ) * 100 + toolbar_percent + ) * 100 + appbar_percent if self._scroll_was_moving: direction = self._get_direction_swipe(self.ids.scroll.scroll_y) @@ -469,38 +386,47 @@ class MDSliverAppbar(MDBoxLayout): "on_scroll_content", self.ids.scroll.scroll_y, direction ) - if self.hide_toolbar: + if self.hide_appbar: if percent_min <= current_percent: opacity = (current_percent - percent_min) / (100 - percent_min) self._opacity = self.max_opacity * (1 - opacity) self.background_color = self.background_color[0:3] + [ 1 - opacity ] - self.toolbar_cls._hard_shadow_a = 1 - opacity - self.toolbar_cls._soft_shadow_a = 1 - opacity else: self.background_color = self.background_color[0:3] + [1] - def get_default_toolbar(self) -> MDTopAppBar: - """Called if no value is passed for the toolbar_cls attribute.""" - - return MDTopAppBar( - pos_hint={"top": 1}, md_bg_color=self.background_color - ) - def add_widget(self, widget, index=0, canvas=None): - if issubclass(widget.__class__, MDSliverAppbarContent): + if isinstance(widget, MDSliverAppbarContent): Clock.schedule_once(lambda x: self._set_radius(widget)) self.ids.scroll_box.add_widget(widget) - elif issubclass(widget.__class__, MDSliverAppbarHeader): + elif isinstance(widget, MDSliverAppbarHeader): self.ids.header.add_widget(widget) + elif isinstance(widget, MDTopAppBar): + self._appbar = widget + widget.pos_hint = {"top": 1} + self.ids.float_box.add_widget(widget) else: super().add_widget(widget, index=index, canvas=canvas) - def _set_radius(self, instance: MDSliverAppbarContent) -> None: + def on__appbar(self, instance, value): + def set_rgba_appbar(*args): + if self.hide_appbar: + value.theme_elevation_level = "Custom" + value.elevation_level = 0 + value.theme_shadow_color = "Custom" + value.shadow_color = self.theme_cls.transparentColor + value.md_bg_color = self.theme_cls.transparentColor + value.canvas.get_group("md-top-app-bar-color")[ + 0 + ].rgba = self.theme_cls.transparentColor + + Clock.schedule_once(set_rgba_appbar, 0.5) + + def _set_radius(self, instance: MDSliverAppbarContent): instance.radius = self.radius - def _get_direction_swipe(self, current_percent: float) -> str: + def _get_direction_swipe(self, current_percent: float): if self._last_scroll_y_pos > current_percent: direction = "up" else: diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__init__.py index a6fd8ec..c83a2cf 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__init__.py @@ -1,6 +1,9 @@ from .snackbar import ( # NOQA F401 MDSnackbar, + MDSnackbarText, + MDSnackbarSupportingText, MDSnackbarActionButton, + MDSnackbarActionButtonText, + MDSnackbarButtonContainer, MDSnackbarCloseButton, - Snackbar, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/__init__.cpython-311.pyc index d68708d..1ef27c8 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/snackbar.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/snackbar.cpython-311.pyc index 86565a6..5ef8995 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/snackbar.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/__pycache__/snackbar.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.kv index 4334bbf..7ed7d7c 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.kv @@ -1,28 +1,78 @@ -#:import SNACK_BAR_ELEVATION kivymd.material_resources.SNACK_BAR_ELEVATION -#:import SNACK_BAR_OFFSET kivymd.material_resources.SNACK_BAR_OFFSET - - padding: 0, 0, "8dp", 0 + theme_bg_color: "Custom" + theme_elevation_level: "Custom" + theme_elevation_level: "Custom" + style: "elevated" + shadow_radius: self.radius + elevation_level: 3 size_hint_y: None height: self.minimum_height - md_bg_color: "#323232" - elevation: SNACK_BAR_ELEVATION - shadow_offset: SNACK_BAR_OFFSET + orientation: "vertical" + md_bg_color: + self.theme_cls.inverseSurfaceColor \ + if not self.background_color else \ + self.background_color - SnackbarLabelContainer: + BoxLayout: id: label_container padding: "16dp", "15dp", 0, "15dp" orientation: "vertical" - adaptive_height: True - pos_hint: {"center_y": .5} + size_hint_y: None + height: self.minimum_height + pos_hint: {"top": 1} spacing: "4dp" - SnackbarActionButtonContainer: - id: action_container + BoxLayout: + id: button_container size_hint_x: None + width: self.minimum_width - SnackbarCloseButtonContainer: - id: close_container - size_hint_x: None - width: "38dp" + + + size_hint_y: None + height: self.minimum_height + + + + adaptive_size: True + font_style: "Label" + role: "large" + markup: True + text_color: + self.theme_cls.inversePrimaryColor \ + if self.theme_text_color == "Primary" else \ + self.text_color + + + + adaptive_height: True + font_style: "Body" + role: "medium" + markup: True + text_color: + self.theme_cls.inverseOnSurfaceColor \ + if self.theme_text_color == "Primary" else \ + self.text_color + + + style: "text" + pos_hint: {"right": 1} + + + + text_color: + self.theme_cls.inverseOnSurfaceColor \ + if self.theme_icon_color == "Primary" else \ + self.icon_color + + + + adaptive_size: True + font_style: "Label" + role: "large" + markup: True + color: + self.theme_cls.inversePrimaryColor \ + if self.theme_text_color == "Primary" else \ + self.text_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.py index fdb7ebe..85c31b5 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/snackbar/snackbar.py @@ -12,228 +12,179 @@ Components/Snackbar .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar.png :align: center +- Snackbars shouldn’t interrupt the user’s experience +- Usually appear at the bottom of the UI +- Can disappear on their own or remain on screen until the user takes action + Usage ----- .. code-block:: python MDSnackbar( - MDLabel( - text="First string", - theme_text_color="Custom", - text_color="#393231", + MDSnackbarText( + text="Text", ), + y=dp(24), + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, ).open() -Example +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-anatomy-detail.png + :align: center + +1. Container +2. Supporting text +3. Action (optional) +4. Icon (optional close affordance) + +Anatomy ------- +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-anatomy.png + :align: center + +Configurations +============== + +1. Single line +-------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-single-line.png + :align: center + .. code-block:: python - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.label import MDLabel - from kivymd.uix.snackbar import MDSnackbar + MDSnackbar( + MDSnackbarText( + text="Single-line snackbar", + ), + y=dp(24), + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() - KV = ''' - MDScreen: +2. Single-line snackbar with action +----------------------------------- - MDRaisedButton: - text: "Create simple snackbar" - on_release: app.open_snackbar() - pos_hint: {"center_x": .5, "center_y": .5} - ''' +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-single-line-with-action.png + :align: center +.. code-block:: python - class Example(MDApp): - def open_snackbar(self): - MDSnackbar( - MDLabel( - text="First string", + MDSnackbar( + MDSnackbarSupportingText( + text="Single-line snackbar with action", + ), + MDSnackbarButtonContainer( + MDSnackbarActionButton( + MDSnackbarActionButtonText( + text="Action button" ), - ).open() - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-simple.gif - :align: center - -Control width and pos ---------------------- - -.. code-block:: python - - MDSnackbar( - MDLabel( - text="First string", - ), - pos=(dp(24), dp(56)), - size_hint_x=0.5, - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-widith-and-pos.gif - :align: center - -On mobile, use up to two lines of text to communicate the snackbar message: - -.. code-block:: python - - MDSnackbar( - MDLabel( - text="First string", - theme_text_color="Custom", - text_color="#393231", - ), - MDLabel( - text="Second string", - theme_text_color="Custom", - text_color="#393231", - ), - y=dp(24), - pos_hint={"center_x": 0.5}, - size_hint_x=0.5, - md_bg_color="#E8D8D7", - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-two-line.gif - :align: center - -Usage action button -------------------- - -A snackbar can contain a single action. "Dismiss" or "cancel" actions are -optional: - -.. code-block:: python - - MDSnackbar( - MDLabel( - text="First string", - theme_text_color="Custom", - text_color="#393231", - ), - MDSnackbarActionButton( - text="Done", - theme_text_color="Custom", - text_color="#8E353C", - ), - y=dp(24), - pos_hint={"center_x": 0.5}, - size_hint_x=0.5, - md_bg_color="#E8D8D7", - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-action-button.gif - :align: center - -Callback action button ----------------------- - -.. code-block:: python - - def snackbar_action_button_callback(self, *args): - print("Snackbar callback action button") - - def open_snackbar(self): - self.snackbar = MDSnackbar( - MDLabel( - text="First string", - theme_text_color="Custom", - text_color="#393231", ), + pos_hint={"center_y": 0.5} + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + +3. Single-line snackbar with action and close buttons +----------------------------------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-single-line-with-action-and-close-buttons.png + :align: center + +.. code-block:: python + + MDSnackbar( + MDSnackbarSupportingText( + text="Single-line snackbar with action and close buttons", + ), + MDSnackbarButtonContainer( MDSnackbarActionButton( - text="Done", - theme_text_color="Custom", - text_color="#8E353C", - _no_ripple_effect=True, - on_release=self.snackbar_action_button_callback, - ), - y=dp(24), - pos_hint={"center_x": 0.5}, - size_hint_x=0.5, - md_bg_color="#E8D8D7", - ) - self.snackbar.open() - -If an action is long, it can be displayed on a third line: - -.. code-block:: python - - MDSnackbar( - MDLabel( - text="If an action is long, it can be displayed", - theme_text_color="Custom", - text_color="#393231", - ), - MDLabel( - text="on a third line.", - theme_text_color="Custom", - text_color="#393231", - ), - MDLabel( - text=" ", - ), - MDSnackbarActionButton( - text="Action button", - theme_text_color="Custom", - text_color="#8E353C", - y=dp(8), - _no_ripple_effect=True, - ), - y=dp(24), - pos_hint={"center_x": 0.5}, - size_hint_x=0.5, - md_bg_color="#E8D8D7", - ).open() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-action-button-on-thrid-line.gif - :align: center - -Icon (optional close affordance): - -.. code-block:: python - - def snackbar_close(self, *args): - self.snackbar.dismiss() - - def open_snackbar(self): - self.snackbar = MDSnackbar( - MDLabel( - text="Icon (optional close affordance)", - theme_text_color="Custom", - text_color="#393231", - ), - MDSnackbarActionButton( - text="Action button", - theme_text_color="Custom", - text_color="#8E353C", - _no_ripple_effect=True, + MDSnackbarActionButtonText( + text="Action button" + ), ), MDSnackbarCloseButton( icon="close", - theme_text_color="Custom", - text_color="#8E353C", - _no_ripple_effect=True, - on_release=self.snackbar_close, ), - y=dp(24), - pos_hint={"center_x": 0.5}, - size_hint_x=0.5, - md_bg_color="#E8D8D7", - ) - self.snackbar.open() + pos_hint={"center_y": 0.5} + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snackbar-optional-close-affordance.gif +4. Two-line snackbar with action and close buttons +-------------------------------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-two-line-with-action-and-close-buttons.png :align: center +.. code-block:: python + + MDSnackbar( + MDSnackbarText( + text="Single-line snackbar", + ), + MDSnackbarSupportingText( + text="with action and close buttons", + ), + MDSnackbarButtonContainer( + MDSnackbarActionButton( + MDSnackbarActionButtonText( + text="Action button" + ), + ), + MDSnackbarCloseButton( + icon="close", + ), + pos_hint={"center_y": 0.5} + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + ).open() + +5. Two-line snackbar with action and close buttons at the bottom +---------------------------------------------------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/snakbar-configurations-two-line-with-action-and-close-buttons-bottom.png + :align: center + +.. code-block:: python + + MDSnackbar( + MDSnackbarText( + text="Single-line snackbar with action", + ), + MDSnackbarSupportingText( + text="and close buttons at the bottom", + padding=[0, 0, 0, dp(56)], + ), + MDSnackbarButtonContainer( + Widget(), + MDSnackbarActionButton( + MDSnackbarActionButtonText( + text="Action button" + ), + ), + MDSnackbarCloseButton( + icon="close", + ), + ), + y=dp(124), + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + padding=[0, 0, "8dp", "8dp"], + ).open() + API break ========= @@ -279,37 +230,62 @@ API break size_hint_x=0.5, md_bg_color="#E8D8D7", ).open() + +2.0.0 version +------------- + +.. code-block:: python + + MDSnackbar( + MDSnackbarSupportingText( + text="Single-line snackbar with action", + ), + MDSnackbarButtonContainer( + MDSnackbarActionButton( + MDSnackbarActionButtonText( + text="Action button" + ), + ), + pos_hint={"center_y": 0.5} + ), + y=dp(24), + orientation="horizontal", + pos_hint={"center_x": 0.5}, + size_hint_x=0.5, + background_color=self.theme_cls.onPrimaryContainerColor, + ).open() """ __all__ = ( "MDSnackbar", + "MDSnackbarText", + "MDSnackbarSupportingText", + "MDSnackbarButtonContainer", "MDSnackbarActionButton", + "MDSnackbarActionButtonText", "MDSnackbarCloseButton", ) import os -from kivy import Logger -from kivy.animation import Animation from kivy.clock import Clock from kivy.core.window import Window from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import ( BooleanProperty, - ColorProperty, ListProperty, NumericProperty, - OptionProperty, - StringProperty, + ColorProperty, ) +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.widget import Widget from kivymd import uix_path -from kivymd.uix.behaviors import MotionShackBehavior -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDFlatButton, MDIconButton +from kivymd.uix.behaviors import MotionShackBehavior, DeclarativeBehavior +from kivymd.uix.button import MDButton, MDIconButton, MDButtonText from kivymd.uix.card import MDCard from kivymd.uix.label import MDLabel -from kivymd.uix.relativelayout import MDRelativeLayout with open( os.path.join(uix_path, "snackbar", "snackbar.kv"), encoding="utf-8" @@ -317,16 +293,25 @@ with open( Builder.load_string(kv_file.read()) -class SnackbarLabelContainer(MDBoxLayout): - """Container for placing snackbar text.""" +class MDSnackbarButtonContainer(DeclarativeBehavior, BoxLayout): + """ + The class implements a container for placing snackbar buttons. + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` classes documentation. + """ -class SnackbarActionButtonContainer(MDRelativeLayout): - """Container for placing snackbar action button.""" + def add_widget(self, widget, *args, **kwargs): + def set_container_width(w): + self.parent.width += w.width + if isinstance( + widget, (MDSnackbarActionButton, MDSnackbarCloseButton, Widget) + ): + Clock.schedule_once(lambda x: set_container_width(widget), 0.2) -class SnackbarCloseButtonContainer(MDRelativeLayout): - """Container for placing snackbar close button.""" + return super().add_widget(widget) class MDSnackbarCloseButton(MDIconButton): @@ -334,28 +319,30 @@ class MDSnackbarCloseButton(MDIconButton): Snackbar closed button class. For more information, see in the - :class:`~kivymd.uix.button.MDIconButton` class documentation. + :class:`~kivymd.uix.button.button.MDIconButton` class documentation. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if not self.y and not self.pos_hint: - self.pos_hint = {"center_y": 0.5} + +class MDSnackbarActionButtonText(MDButtonText): + """ + The class implements the text for the :class:`~MDSnackbarActionButton` + class. + + .. versionchanged:: 2.2.0 + + For more information, see in the + :class:`~kivymd.uix.button.button.MDButtonText` class documentation. + """ -class MDSnackbarActionButton(MDFlatButton): +class MDSnackbarActionButton(MDButton): """ Snackbar action button class. For more information, see in the - :class:`~kivymd.uix.button.MDFlatButton` class documentation. + :class:`~kivymd.uix.button.button.MDButton` class documentation. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if not self.y and not self.pos_hint: - self.pos_hint = {"center_y": 0.5} - class MDSnackbar(MotionShackBehavior, MDCard): """ @@ -365,15 +352,15 @@ class MDSnackbar(MotionShackBehavior, MDCard): Rename `BaseSnackbar` to `MDSnackbar` class. For more information, see in the - :class:`~kivymd.uix.card.MDCard` and - :class:`~kivymd.uix.behaviors.StencilBehavior` + :class:`~kivymd.uix.behaviors.motion_behavior.MotionShackBehavior` and + :class:`~kivymd.uix.card.card.MDCard` and class documentation. :Events: :attr:`on_open` - Called when a snackbar opened. + Fired when a snackbar opened. :attr:`on_dismiss` - Called when a snackbar closes. + Fired when a snackbar closes. """ duration = NumericProperty(3) @@ -392,70 +379,22 @@ class MDSnackbar(MotionShackBehavior, MDCard): and defaults to `True`. """ - radius = ListProperty([5, 5, 5, 5]) + radius = ListProperty([dp(4), dp(4), dp(4), dp(4)]) """ Snackbar radius. :attr:`radius` is a :class:`~kivy.properties.ListProperty` - and defaults to `[5, 5, 5, 5]` + and defaults to `[dp(4), dp(4), dp(4), dp(4)]` """ - bg_color = ColorProperty(None, deprecated=True) + background_color = ColorProperty(None) """ - Snackbar background color in (r, g, b, a) or string format. + The background color in (r, g, b, a) or string format of the snackbar. - .. deprecated:: 1.2.0 - Use 'md_bg_color` instead. - - :attr:`bg_color` is a :class:`~kivy.properties.ColorProperty` + :attr:`background_color` is a :class:`~kivy.properties.ColorProperty` and defaults to `None`. """ - buttons = ListProperty(deprecated=True) - """ - Snackbar buttons. - - .. deprecated:: 1.2.0 - - :attr:`buttons` is a :class:`~kivy.properties.ListProperty` - and defaults to `[]` - """ - - snackbar_animation_dir = OptionProperty( - "Bottom", - options=["Top", "Bottom", "Left", "Right"], - deprecated=True, - ) - """ - Snackbar animation direction. - Available options are: `'Top'`, `'Bottom'`, `'Left'`, `'Right'`. - - .. deprecated:: 1.2.0 - - :attr:`snackbar_animation_dir` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Bottom'`. - """ - - snackbar_x = NumericProperty(0, deprecated=True) - """ - The snackbar x position in the screen - - .. deprecated:: 1.2.0 - - :attr:`snackbar_x` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - snackbar_y = NumericProperty(0, deprecated=True) - """ - The snackbar x position in the screen - - .. deprecated:: 1.2.0 - - :attr:`snackbar_y` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.register_event_type("on_open") @@ -470,80 +409,40 @@ class MDSnackbar(MotionShackBehavior, MDCard): def open(self) -> None: """Show the snackbar.""" - for widget in Window.parent.children: - if widget.__class__ is MDSnackbar: - return - - Window.parent.add_widget(self) + Window.add_widget(self) super().on_open() def add_widget(self, widget, *args, **kwargs): - def check_color(color): - if not widget.text_color: - widget.theme_text_color = "Custom" - widget.text_color = color - - if isinstance(widget, MDSnackbarCloseButton): - widget.icon_size = "20sp" - check_color("white") - self.ids.close_container.add_widget(widget) - if len(self.ids.close_container.children) >= 2: - Logger.warning( - "KivyMD: " - "Do not use more than one button to close the snackbar. " - "This is contrary to the material design rules " - "of version 3" - ) - if isinstance(widget, MDSnackbarActionButton): - self.ids.action_container.add_widget(widget) - check_color(self.theme_cls.primary_color) - if len(self.ids.action_container.children) >= 2: - Logger.warning( - "KivyMD: " - "Do not use more than one action button. " - "This is contrary to the material design rules " - "of version 3" - ) - if isinstance(widget, MDLabel): - widget.adaptive_height = True - widget.pos_hint = {"center_y": 0.5} - check_color("white") + if isinstance(widget, (MDSnackbarText, MDSnackbarSupportingText)): self.ids.label_container.add_widget(widget) - if len(self.ids.label_container.children) >= 4: - Logger.warning( - "KivyMD: " - "Do not use more than three lines in the snackbar. " - "This is contrary to the material design rules " - "of version 3" - ) - elif isinstance( - widget, - ( - SnackbarLabelContainer, - SnackbarActionButtonContainer, - SnackbarCloseButtonContainer, - ), - ): + elif isinstance(widget, MDSnackbarButtonContainer): + self.ids.button_container.size_hint_x = ( + 1 if self.orientation == "vertical" else None + ) + self.ids.button_container.add_widget(widget) + else: return super().add_widget(widget) def on_open(self, *args) -> None: - """Called when a snackbar opened.""" + """Fired when a snackbar opened.""" def on_dismiss(self, *args) -> None: - """Called when a snackbar closed.""" + """Fired when a snackbar closed.""" -class Snackbar(MDSnackbar): +class MDSnackbarText(MDLabel): """ - .. deprecated:: 1.2.0 - Use :class:`~kivymd.uix.snackbar.MDSnackbar` - class instead. + The class implements the text. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - Logger.warning( - "KivyMD: " - "The `Snackbar` class has been deprecated. " - "Use the `MDSnackbar` class instead." - ) + +class MDSnackbarSupportingText(MDLabel): + """ + The class implements the supporting text. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__init__.py deleted file mode 100644 index a1d79e1..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .spinner import MDSpinner # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 8a688a9..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__pycache__/spinner.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__pycache__/spinner.cpython-311.pyc deleted file mode 100644 index e1a4d8c..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/__pycache__/spinner.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/spinner.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/spinner.kv deleted file mode 100644 index a532803..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/spinner.kv +++ /dev/null @@ -1,18 +0,0 @@ - - canvas.before: - PushMatrix - Rotate: - angle: self._rotation_angle - origin: self.center - canvas: - Color: - rgba: self.color if self.color else self.theme_cls.primary_color - a: self._alpha - SmoothLine: - cap: 'square' - width: root.line_width - circle: - self.center_x, self.center_y, self.width / 2, \ - self._angle_start, self._angle_end - canvas.after: - PopMatrix diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/spinner.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/spinner.py deleted file mode 100644 index 6bf3c33..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/spinner/spinner.py +++ /dev/null @@ -1,321 +0,0 @@ -""" -Components/Spinner -================== - -.. seealso:: - - `Material Design spec, Menus `_ - -.. rubric:: Circular progress indicator in Google's Material Design. - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDSpinner: - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': .5, 'center_y': .5} - active: True if check.active else False - - MDCheckbox: - id: check - size_hint: None, None - size: dp(48), dp(48) - pos_hint: {'center_x': .5, 'center_y': .4} - active: True - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner.gif - :align: center - -Spinner palette ---------------- - -.. code-block:: kv - - MDSpinner: - # The number of color values ​​can be any. - palette: - [0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1], \ - [0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1], \ - [0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1], \ - [0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1], - -.. code-block:: python - - MDSpinner( - size_hint=(None, None), - size=(dp(46), dp(46)), - pos_hint={'center_x': .5, 'center_y': .5}, - active=True, - palette=[ - [0.28627450980392155, 0.8431372549019608, 0.596078431372549, 1], - [0.3568627450980392, 0.3215686274509804, 0.8666666666666667, 1], - [0.8862745098039215, 0.36470588235294116, 0.592156862745098, 1], - [0.8784313725490196, 0.9058823529411765, 0.40784313725490196, 1], - ] - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner-palette.gif - :align: center - -Determinate mode ----------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDSpinner: - size_hint: None, None - size: dp(48), dp(48) - pos_hint: {'center_x': .5, 'center_y': .5} - determinate: True - ''' - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/spinner-determinate.gif - :align: center -""" - -__all__ = ("MDSpinner",) - -import os -from typing import Union - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, -) -from kivy.uix.widget import Widget - -from kivymd import uix_path -from kivymd.theming import ThemableBehavior - -with open( - os.path.join(uix_path, "spinner", "spinner.kv"), encoding="utf-8" -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDSpinner(ThemableBehavior, Widget): - """ - :class:`MDSpinner` is an implementation of the circular progress - indicator in `Google's Material Design`. - - For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.widget.Widget` classes documentation. - - It can be used either as an indeterminate indicator that loops while - the user waits for something to happen, or as a determinate indicator. - - Set :attr:`determinate` to **True** to activate determinate mode, and - :attr:`determinate_time` to set the duration of the animation. - - :Events: - `on_determinate_complete` - The event is called at the end of the spinner loop in the - `determinate = True` mode. - """ - - determinate = BooleanProperty(False) - """ - Determinate value. - - :attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - determinate_time = NumericProperty(2) - """ - Determinate time value. - - :attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - line_width = NumericProperty(dp(2.25)) - """ - Progress line width of spinner. - - :attr:`line_width` is a :class:`~kivy.properties.NumericProperty` - and defaults to `dp(2.25)`. - """ - - active = BooleanProperty(True) - """ - Use :attr:`active` to start or stop the spinner. - - :attr:`active` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - color = ColorProperty(None, allownone=True) - """ - Spinner color in (r, g, b, a) or string format. - - :attr:`color` is a :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - palette = ListProperty() - """ - A set of colors. Changes with each completed spinner cycle. - - :attr:`palette` is a :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - _alpha = NumericProperty(0) - _rotation_angle = NumericProperty(360) - _angle_start = NumericProperty(0) - _angle_end = NumericProperty(0) - _palette = [] - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if not self.color: - self.color = self.theme_cls.primary_color - if self.color == self.theme_cls.primary_color: - self.theme_cls.bind(primary_color=self._update_color) - - self._alpha_anim_in = Animation(_alpha=1, duration=0.8, t="out_quad") - self._alpha_anim_out = Animation(_alpha=0, duration=0.3, t="out_quad") - self._alpha_anim_out.bind( - on_complete=self._reset, - on_progress=self._on_determinate_progress, - ) - self.register_event_type("on_determinate_complete") - Clock.schedule_once(self.check_determinate) - - def on__rotation_angle(self, *args): - if self._rotation_angle == 0: - self._rotation_angle = 360 - if not self.determinate: - _rot_anim = Animation(_rotation_angle=0, duration=2) - _rot_anim.start(self) - elif self._rotation_angle == 360: - if self._palette: - try: - Animation(color=next(self._palette), duration=2).start(self) - except StopIteration: - self._palette = iter(self.palette) - Animation(color=next(self._palette), duration=2).start(self) - - def on_palette(self, instance_spinner, palette_list: list) -> None: - self._palette = iter(palette_list) - - def on_active(self, instance_spinner, active_value: bool) -> None: - self._reset() - if self.active: - self.check_determinate() - - def on_determinate_complete(self, *args): - """ - The event is called at the end of the spinner loop in the - `determinate = True` mode. - """ - - def check_determinate(self, interval: Union[float, int] = 0) -> None: - if self.active: - if self.determinate: - self._start_determinate() - else: - self._start_loop() - - def _update_color(self, *args): - self.color = self.theme_cls.primary_color - - def _start_determinate(self, *args): - self._alpha_anim_in.start(self) - Animation( - _rotation_angle=0, - duration=self.determinate_time * 0.7, - t="out_quad", - ).start(self) - - _angle_start_anim = Animation( - _angle_end=360, duration=self.determinate_time, t="in_out_quad" - ) - _angle_start_anim.bind( - on_complete=lambda *x: self._alpha_anim_out.start(self) - ) - - _angle_start_anim.start(self) - - def _start_loop(self, *args): - if self._alpha == 0: - _rot_anim = Animation(_rotation_angle=0, duration=2, t="linear") - _rot_anim.start(self) - - self._alpha = 1 - self._alpha_anim_in.start(self) - _angle_start_anim = Animation( - _angle_end=self._angle_end + 270, duration=0.6, t="in_out_cubic" - ) - _angle_start_anim.bind(on_complete=self._anim_back) - _angle_start_anim.start(self) - - def _anim_back(self, *args): - _angle_back_anim = Animation( - _angle_start=self._angle_end - 8, duration=0.6, t="in_out_cubic" - ) - _angle_back_anim.bind(on_complete=self._start_loop) - - _angle_back_anim.start(self) - - def _reset(self, *args): - Animation.cancel_all( - self, - "_angle_start", - "_rotation_angle", - "_angle_end", - "_alpha", - "color", - ) - self._angle_start = 0 - self._angle_end = 0 - self._rotation_angle = 360 - self._alpha = 0 - - def _on_determinate_progress( - self, instance_animation, instance_spinner, value - ): - if value == 1: - self.dispatch("on_determinate_complete") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/stacklayout.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/stacklayout.py index c5028ca..2174534 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/stacklayout.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/stacklayout.py @@ -16,7 +16,7 @@ StackLayout canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor Rectangle: pos: self.pos size: self.size @@ -28,7 +28,7 @@ MDStackLayout MDStackLayout: adaptive_height: True - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor Available options are: ---------------------- @@ -38,6 +38,7 @@ Available options are: - adaptive_size_ .. adaptive_height: + adaptive_height --------------- @@ -53,6 +54,7 @@ Equivalent height: self.minimum_height .. adaptive_width: + adaptive_width -------------- @@ -68,6 +70,7 @@ Equivalent width: self.minimum_width .. adaptive_size: + adaptive_size ------------- @@ -89,13 +92,24 @@ from kivy.uix.stacklayout import StackLayout from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior class MDStackLayout( - DeclarativeBehavior, ThemableBehavior, StackLayout, MDAdaptiveWidget + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + StackLayout, + MDAdaptiveWidget, ): """ - Stack layout class. For more information, see in the - :class:`~kivy.uix.stacklayout.StackLayout` class documentation. + Stack layout class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.stacklayout.StackLayout` and + :class:`~kivymd.uix.MDAdaptiveWidget` + classes documentation. """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/__init__.cpython-311.pyc index 855e014..9cf3dc4 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/swiper.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/swiper.cpython-311.pyc index e95fbbc..24a911b 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/swiper.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/__pycache__/swiper.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/swiper.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/swiper.py index e169c91..eb051e3 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/swiper.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/swiper/swiper.py @@ -23,28 +23,25 @@ Example .. code-block:: python - from kivymd.app import MDApp from kivy.lang.builder import Builder + from kivymd.app import MDApp + kv = ''' FitImage: - source: "guitar.png" - radius: [20,] + source: "bg.jpg" + radius: [dp(20),] + MDScreen: - - MDTopAppBar: - id: toolbar - title: "MDSwiper" - elevation: 4 - pos_hint: {"top": 1} + md_bg_color: self.theme_cls.backgroundColor MDSwiper: size_hint_y: None - height: root.height - toolbar.height - dp(40) - y: root.height - self.height - toolbar.height - dp(20) + height: root.height - dp(40) + y: root.height - self.height - dp(20) MySwiper: @@ -60,11 +57,13 @@ Example class Main(MDApp): def build(self): + self.theme_cls.theme_style = "Dark" return Builder.load_string(kv) + Main().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdswiper-example.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/swiper-example.gif :align: center .. warning:: @@ -96,103 +95,6 @@ Example on_overswipe_left: print("on_overswipe_left") on_swipe_left: print("on_swipe_left") on_swipe_right: print("on_swipe_right") - -Example -======= - -.. code-block:: python - - from kivy.lang.builder import Builder - - from kivymd.app import MDApp - - kv = ''' - - - - - - RelativeLayout: - - FitImage: - source: "guitar.png" - radius: [20,] - - MDBoxLayout: - adaptive_height: True - spacing: "12dp" - - MagicButton: - id: icon - icon: "weather-sunny" - user_font_size: "56sp" - opposite_colors: True - - MDLabel: - text: "MDLabel" - font_style: "H5" - size_hint_y: None - height: self.texture_size[1] - pos_hint: {"center_y": .5} - opposite_colors: True - - - MDScreen: - - MDTopAppBar: - id: toolbar - title: "MDSwiper" - elevation: 4 - pos_hint: {"top": 1} - - MDSwiper: - size_hint_y: None - height: root.height - toolbar.height - dp(40) - y: root.height - self.height - toolbar.height - dp(20) - on_swipe: self.get_current_item().ids.icon.shake() - - MySwiper: - - MySwiper: - - MySwiper: - - MySwiper: - - MySwiper: - ''' - - - class Main(MDApp): - def build(self): - return Builder.load_string(kv) - - - Main().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mdswiper-on-swipe.gif - :align: center - -How to automatically switch a SwiperItem? -========================================= - -Use method :attr:`~MDSwiper.set_current` which takes the index of :class:`MDSwiperItem` as argument. - -Example -======= - -.. code-block:: kv - - MDSwiper: - id: swiper - - MDSwiperItem: # First widget with index 0 - - MDSwiperItem: # Second widget with index 1 - - MDRaisedButton: - text: "Go to Second" - on_release: swiper.set_current(1) """ __all__ = ("MDSwiperItem", "MDSwiper") @@ -211,11 +113,11 @@ from kivy.properties import ( StringProperty, ) from kivy.uix.anchorlayout import AnchorLayout +from kivy.uix.scrollview import ScrollView from kivy.utils import platform from kivymd import uix_path from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.scrollview import MDScrollView with open( os.path.join(uix_path, "swiper", "swiper.kv"), encoding="utf-8" @@ -223,6 +125,8 @@ with open( Builder.load_string(kv_file.read()) +# TODO: Redesign this module according to the specification - +# https://m3.material.io/components/carousel/overview class _ScrollViewHardStop(DampedScrollEffect): def stop(self, val, t=None): return super().stop(val, t=0.01) @@ -294,12 +198,12 @@ class MDSwiperItem(MDBoxLayout): anim.start(self) -class MDSwiper(MDScrollView): +class MDSwiper(ScrollView): """ Swiper class. For more information, see in the - :class:`~kivymd.uix.scrollview.MDScrollView` class documentation. + :class:`~kivy.uix.scrollview.ScrollView` class documentation. """ items_spacing = NumericProperty("20dp") diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__init__.py index c511198..6a38fdf 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__init__.py @@ -1 +1,10 @@ -from .tab import MDTabs, MDTabsBase, MDTabsLabel # NOQA F401 +from .tab import ( + MDTabsPrimary, + MDTabsSecondary, + MDTabsItem, + MDTabsItemSecondary, + MDTabsItemIcon, + MDTabsItemText, + MDTabsCarousel, + MDTabsBadge, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/__init__.cpython-311.pyc index 640be71..9291e70 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/tab.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/tab.cpython-311.pyc index 655797c..e34a01f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/tab.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/__pycache__/tab.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.kv index f219bc1..93f8913 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.kv @@ -1,128 +1,94 @@ #:import DampedScrollEffect kivy.effects.dampedscroll.DampedScrollEffect - - _set_start_tab: False - size_hint: None, 1 - halign: "center" - valign: "center" - group: "tabs" - font: root.font_name - allow_no_selection: False - markup: True - on_width: - if not self._set_start_tab: \ - self.tab_bar.parent._update_indicator( \ - self.tab_bar.parent.carousel.current_slide.tab_label); \ - self._set_start_tab = True - on_tab_bar: - self.text_size = (None, None) \ - if self.tab_bar.parent.allow_stretch else (self.width, None) - on_ref_press: - self.tab_bar.parent.dispatch( \ - "on_ref_press", - self, \ - self.tab, \ - self.tab_bar, \ - self.tab_bar.parent.carousel) - color: - ( \ - self.text_color_active \ - if self.text_color_active else self.specific_secondary_text_color \ - ) \ - if self.state == "down" else \ - ( \ - self.text_color_normal \ - if self.text_color_normal else self.theme_cls.text_color \ - ) + + orientation: "vertical" + size_hint_y: None + height: self.minimum_height + + MDTabsScrollView: + id: tab_scroll + do_scroll_x: False if container.width <= self.width else True + + canvas.before: + Color: + rgba: + root.md_bg_color \ + if root.md_bg_color and root.theme_bg_color == "Custom" else \ + root.theme_cls.surfaceColor + Rectangle: + pos: self.pos + size: self.size + + GridLayout: + id: container + rows: 1 + size_hint: None, None + width: self.minimum_width + height: root.height + + canvas.before: + Color: + rgba: root.theme_cls.primaryColor + SmoothRoundedRectangle: + group: "md-tabs-rounded-rectangle" + pos: + self.x, \ + self.y \ + if not root._tabs_carousel else \ + root._tabs_carousel.height + size: 0, root.indicator_height + radius: root.indicator_radius + + + + pos_hint: {"center_x": .5} + theme_icon_color: "Custom" + icon_color: self.theme_cls.onSurfaceVariantColor + + + + adaptive_size: True + pos_hint: {"center_x": .5, "center_y": .5} + padding_x: "36dp" + font_style: "Title" + role: "small" + theme_text_color: "Custom" + text_color: self.theme_cls.onSurfaceVariantColor + + + + orientation: "vertical" + size_hint: None, None + height: self.minimum_height + spacing: "4dp" + padding: 0, "12dp", 0, "8dp" + + + + + + + size_hint: None, None + height: "48dp" + anchor_x: "center" + anchor_y: "center" + + MDTabsItemSecondaryContainer: + id: box_container + size_hint: None, None + size: self.minimum_size + spacing: "8dp" - size_hint: 1, 1 + size_hint: 1, None do_scroll_y: False - bar_color: 0, 0, 0, 0 - bar_inactive_color: 0, 0, 0, 0 bar_width: 0 effect_cls: DampedScrollEffect - - carousel: carousel - tab_bar: tab_bar - anchor_y: "top" - background_palette: "Primary" + - _line_x: 0 - _line_width: 0 - _line_height: 0 - _line_radius: 0 - on_size: - root._update_padding(layout) - - MDTabsMain: - padding: 0, tab_bar.height, 0, 0 - - MDTabsCarousel: - id: carousel - lock_swiping: root.lock_swiping - ignore_perpendicular_swipes: True - anim_move_duration: root.anim_duration - on_index: root.on_carousel_index(*args) - on__offset: tab_bar.android_animation(*args) - - MDTabsBar: - id: tab_bar - padding: root.tab_padding - carousel: carousel - scrollview: scrollview - layout: layout - size_hint: 1, None - elevation: root.elevation - radius: root.radius - shadow_offset: root.shadow_offset - shadow_color: root.shadow_color - shadow_softness: root.shadow_softness - height: root.tab_bar_height - md_bg_color: - self.theme_cls.primary_color \ - if not root.background_color else \ - root.background_color - - MDTabsScrollView: - id: scrollview - do_scroll_x: False if layout.width <= self.width else True - - MDGridLayout: - id: layout - rows: 1 - size_hint_y: 1 - adaptive_width: True - on_size: root._update_padding(layout) - - canvas.before: - Color: - rgba: root.underline_color - Line: - width: dp(2) - rectangle: [0, 0, layout.width, dp(2)] - Color: - rgba: - root.theme_cls.accent_color \ - if not root.indicator_color else \ - root.indicator_color - RoundedRectangle: - group: "Indicator_line" - pos: self.pos - size: 0, root.tab_indicator_height - radius: [0,] - Line: - width: dp(2) - rounded_rectangle: - [ \ - root._line_x, \ - self.pos[1], \ - root._line_width, \ - root._line_height, \ - root._line_radius \ - ] + diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.py index 4e97bef..dc76033 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tab/tab.py @@ -4,1195 +4,592 @@ Components/Tabs .. seealso:: - `Material Design spec, Tabs `_ + `Material Design spec, Tabs `_ .. rubric:: Tabs organize content across different screens, data sets, and other interactions. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-preview.png :align: center -.. Note:: Module provides tabs in the form of icons or text. +- Use tabs to group content into helpful categories +- Two types: primary and secondary +- Tabs can horizontally scroll, so a UI can have as many tabs as needed +- Place tabs next to each other as peers -Usage ------ +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-types.png + :align: center -To create a tab, you must create a new class that inherits from the -:class:`~MDTabsBase` class and the `Kivy` container, in which you will create -content for the tab. +1. Primary tabs +2. Secondary tabs + +Usage primary tabs +------------------ + +Primary tabs should be used when just one set of tabs are needed. .. code-block:: python + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.tab import ( + MDTabsItem, + MDTabsItemIcon, + MDTabsItemText, + MDTabsBadge, + ) + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTabsPrimary: + id: tabs + pos_hint: {"center_x": .5, "center_y": .5} + + MDDivider: + ''' + + + class Example(MDApp): + def on_start(self): + for tab_icon, tab_name in { + "airplane": "Flights", + "treasure-chest": "Trips", + "compass-outline": "Explore", + }.items(): + if tab_icon == "treasure-chest": + self.root.ids.tabs.add_widget( + MDTabsItem( + MDTabsItemIcon( + MDTabsBadge( + text="99", + ), + icon=tab_icon, + ), + MDTabsItemText( + text=tab_name, + ), + ) + ) + else: + self.root.ids.tabs.add_widget( + MDTabsItem( + MDTabsItemIcon( + icon=tab_icon, + ), + MDTabsItemText( + text=tab_name, + ), + ) + ) + self.root.ids.tabs.switch_tab(icon="airplane") + + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-usage.png + :align: center + +Anatomy primary tabs +-------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-anatomy.png + :align: center + +Usage secondary tabs +-------------------- + +Secondary tabs are necessary when a screen requires more than one level of +tabs. These tabs use a simpler style of indicator, but their function is +identical to primary tabs. + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.tab import ( + MDTabsItemIcon, + MDTabsItemText, + MDTabsBadge, MDTabsItemSecondary, + ) + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTabsSecondary: + id: tabs + pos_hint: {"center_x": .5, "center_y": .5} + + MDDivider: + ''' + + + class Example(MDApp): + def on_start(self): + for tab_icon, tab_name in { + "airplane": "Flights", + "treasure-chest": "Trips", + "compass-outline": "Explore", + }.items(): + if tab_icon == "treasure-chest": + self.root.ids.tabs.add_widget( + MDTabsItemSecondary( + MDTabsItemIcon( + icon=tab_icon, + ), + MDTabsItemText( + text=tab_name, + ), + MDTabsBadge( + text="5", + ), + ) + ) + else: + self.root.ids.tabs.add_widget( + MDTabsItemSecondary( + MDTabsItemIcon( + icon=tab_icon, + ), + MDTabsItemText( + text=tab_name, + ), + ) + ) + self.root.ids.tabs.switch_tab(icon="airplane") + + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + + Example().run() + +Anatomy secondary tabs +---------------------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-secondary-anatomy.png + :align: center + +Related content +--------------- + +Use tabs to group related content, not sequential content. + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.label import MDLabel + from kivymd.uix.tab import ( + MDTabsItemIcon, + MDTabsItemText, + MDTabsItem, + ) + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTabsPrimary: + id: tabs + pos_hint: {"center_x": .5, "center_y": .5} + size_hint_x: .6 + + MDDivider: + + MDTabsCarousel: + id: related_content_container + size_hint_y: None + height: dp(320) + ''' + + + class Example(MDApp): + def on_start(self): + for tab_icon, tab_name in { + "airplane": "Flights", + "treasure-chest": "Trips", + "compass-outline": "Explore", + }.items(): + self.root.ids.tabs.add_widget( + MDTabsItem( + MDTabsItemIcon( + icon=tab_icon, + ), + MDTabsItemText( + text=tab_name, + ), + ) + ) + self.root.ids.related_content_container.add_widget( + MDLabel( + text=tab_name, + halign="center", + ) + ) + self.root.ids.tabs.switch_tab(icon="airplane") + + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-related-content.gif + :align: center + +Behaviors +========= + +Scrollable tabs +--------------- + +When a set of tabs cannot fit on screen, use scrollable tabs. Scrollable tabs +can use longer text labels and a larger number of tabs. They are best used for +browsing on touch interfaces. + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.tab import MDTabsItemText, MDTabsItem + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTabsPrimary: + id: tabs + pos_hint: {"center_x": .5, "center_y": .5} + size_hint_x: .6 + allow_stretch: False + label_only: True + + MDDivider: + ''' + + + class Example(MDApp): + def on_start(self): + for tab_name in [ + "Moscow", + "Saint Petersburg", + "Novosibirsk", + "Yekaterinburg", + "Kazan", + "Nizhny Novgorod", + "Chelyabinsk", + ]: + self.root.ids.tabs.add_widget( + MDTabsItem( + MDTabsItemText( + text=tab_name, + ), + ) + ) + self.root.ids.tabs.switch_tab(text="Moscow") + + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-scrollable-behavior.gif + :align: center + +Fixed tabs +========== + +Fixed tabs display all tabs in a set simultaneously. They are best for +switching between related content quickly, such as between transportation +methods in a map. To navigate between fixed tabs, tap an individual tab, or +swipe left or right in the content area. + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.tab import MDTabsItemText, MDTabsItem + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTabsPrimary: + id: tabs + pos_hint: {"center_x": .5, "center_y": .5} + size_hint_x: .6 + allow_stretch: True + label_only: True + + MDDivider: + ''' + + + class Example(MDApp): + def on_start(self): + for tab_name in [ + "Moscow", "Saint Petersburg", "Novosibirsk" + ]: + self.root.ids.tabs.add_widget( + MDTabsItem( + MDTabsItemText( + text=tab_name, + ), + ) + ) + self.root.ids.tabs.switch_tab(text="Moscow") + + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-fixed-behavior.png + :align: center + +Tap a tab +--------- + +Navigate to a tab by tapping on it. + + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-tap-a-tab-behavior.gif + :align: center + +Swipe within the content area +----------------------------- + +To navigate between tabs, users can swipe left or right within the content +area. + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-swipe-within-content-area-behavior.gif + :align: center + +Switching tab +============= + +You can switch tabs by icon name, by tab name, and by tab objects: + +.. code-block:: python + + instance_tabs.switch_tab(icon="airplane") + +.. code-block:: python + + instance_tabs.switch_tab(text="Airplane") + +.. code-block:: python + + instance_tabs.switch_tab( + instance=instance_tabs_item # MDTabsItem + ) + +API break +========= + +1.2.0 version +------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.uix.floatlayout import MDFloatLayout + from kivymd.uix.tab import MDTabsBase + from kivymd.icon_definitions import md_icons + + KV = ''' + MDBoxLayout: + + MDTabs: + id: tabs + on_ref_press: app.on_ref_press(*args) + + + + + MDIconButton: + id: icon + icon: app.icons[0] + icon_size: "48sp" + pos_hint: {"center_x": .5, "center_y": .5} + ''' + + class Tab(MDFloatLayout, MDTabsBase): '''Class implementing content for a tab.''' -.. code-block:: kv - + class Example(MDApp): + icons = list(md_icons.keys())[15:30] - MDLabel: - text: root.content_text + def build(self): + return Builder.load_string(KV) + + def on_start(self): + for name_tab in self.icons: + self.root.ids.tabs.add_widget( + Tab(title=name_tab, icon=name_tab) + ) + + + Example().run() + +2.0.0 version +------------- + +.. code-block:: python + + from kivy.lang import Builder + + from kivymd.app import MDApp + from kivymd.icon_definitions import md_icons + from kivymd.uix.label import MDIcon + from kivymd.uix.tab import MDTabsItem, MDTabsItemIcon + from kivymd.uix.tab.tab import MDTabsItemText + + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + MDTabsPrimary: + id: tabs + allow_stretch: False pos_hint: {"center_x": .5, "center_y": .5} -All tabs must be contained inside a :class:`~MDTabs` widget: + MDDivider: -.. code-block:: kv - - Root: - - MDTabs: - - Tab: - title: "Tab 1" - content_text: f"This is an example text for {self.title}" - - Tab: - title: "Tab 2" - content_text: f"This is an example text for {self.title}" - - ... - -Example with tab icon ---------------------- - -.. tabs:: - - .. tab:: Declarative KV and imperative python styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - on_tab_switch: app.on_tab_switch(*args) + MDTabsCarousel: + id: related_content + size_hint_y: None + height: root.height - tabs.ids.tab_scroll.height + ''' - - - MDIconButton: - id: icon - icon: root.icon - icon_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_start(self): - for tab_name in self.icons: - self.root.ids.tabs.add_widget(Tab(icon=tab_name)) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - ''' - Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - - count_icon = instance_tab.icon # get the tab icon - print(f"Welcome to {count_icon}' tab'") - - - Example().run() - - .. tab:: Declarative python styles - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDIconButton - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons - from kivymd.uix.toolbar import MDTopAppBar - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDBoxLayout( - MDTopAppBar(title="Example Tabs"), - MDTabs(id="tabs"), - orientation="vertical", - ) + class Example(MDApp): + def on_start(self): + for name_tab in list(md_icons.keys())[15:30]: + self.root.ids.tabs.add_widget( + MDTabsItem( + MDTabsItemIcon( + icon=name_tab, + ), + MDTabsItemText( + text=name_tab, + ), ) - - def on_start(self): - self.root.ids.tabs.bind(on_tab_switch=self.on_tab_switch) - - for tab_name in self.icons: - self.root.ids.tabs.add_widget( - Tab( - MDIconButton( - icon=tab_name, - icon_size="48sp", - pos_hint={"center_x": .5, "center_y": .5}, - ), - icon=tab_name, - ) - ) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - ''' - Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - - count_icon = instance_tab.icon # get the tab icon - print(f"Welcome to {count_icon}' tab'") - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example.gif - :align: center - -Example with tab text ---------------------- - -.. Note:: The :class:`~MDTabsBase` class has an icon parameter and, by default, - tries to find the name of the icon in the file - ``kivymd/icon_definitions.py``. - - If the name of the icon is not found, the class will send a message - stating that the icon could not be found. - - if the tab has no icon, title or tab_label_text, the class will raise a - ValueError. - -.. tabs:: - - .. tab:: Declarative KV and imperative python styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - on_tab_switch: app.on_tab_switch(*args) - - - - - MDLabel: - id: label - text: "Tab 0" - halign: "center" - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_start(self): - for i in range(20): - self.root.ids.tabs.add_widget(Tab(title=f"Tab {i}")) - - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - '''Called when switching tabs. - - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - - instance_tab.ids.label.text = tab_text - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.label import MDLabel - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.uix.toolbar import MDTopAppBar - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDBoxLayout( - MDTopAppBar(title="Example Tabs"), - MDTabs(id="tabs"), - orientation="vertical", - ) + ) + self.root.ids.related_content.add_widget( + MDIcon( + icon=name_tab, + pos_hint={"center_x": 0.5, "center_y": 0.5}, ) + ) + self.root.ids.tabs.switch_tab(icon="airplane") - def on_start(self): - self.root.ids.tabs.bind(on_tab_switch=self.on_tab_switch) - for i in range(20): - self.root.ids.tabs.add_widget( - Tab( - MDLabel(id="label", text="Tab 0", halign="center"), - title=f"Tab {i}", - ) - ) + def build(self): + return Builder.load_string(KV) - def on_tab_switch( - self, instance_tabs, instance_tab, instance_tab_label, tab_text - ): - ''' - Called when switching tabs. - :type instance_tabs: ; - :param instance_tab: <__main__.Tab object>; - :param instance_tab_label: ; - :param tab_text: text or name icon of tab; - ''' - - instance_tab.ids.label.text = tab_text - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-text.gif - :align: center - -Example with tab icon and text ------------------------------- - -.. tabs:: - - .. tab:: Declarative KV and imperative python styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - pass - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_start(self): - for name_tab in list(md_icons.keys())[15:30]: - self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab)) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.icon_definitions import md_icons - from kivymd.uix.toolbar import MDTopAppBar - - - class Tab(MDFloatLayout, MDTabsBase): - pass - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDBoxLayout( - MDTopAppBar(title="Example Tabs"), - MDTabs(id="tabs"), - orientation="vertical", - ) - ) - - def on_start(self): - for name_tab in list(md_icons.keys())[15:30]: - self.root.ids.tabs.add_widget(Tab(icon=name_tab, title=name_tab)) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-simple-example-icon-text.gif - :align: center - -Dynamic tab management ----------------------- - -.. tabs:: - - .. tab:: Declarative KV and imperative python styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.uix.scrollview import MDScrollView - from kivymd.app import MDApp - from kivymd.uix.tab import MDTabsBase - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - - - - - MDList: - - MDBoxLayout: - adaptive_height: True - - MDFlatButton: - text: "ADD TAB" - on_release: app.add_tab() - - MDFlatButton: - text: "REMOVE LAST TAB" - on_release: app.remove_tab() - - MDFlatButton: - text: "GET TAB LIST" - on_release: app.get_tab_list() - ''' - - - class Tab(MDScrollView, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - index = 0 - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_start(self): - self.add_tab() - - def get_tab_list(self): - '''Prints a list of tab objects.''' - - print(self.root.ids.tabs.get_tab_list()) - - def add_tab(self): - self.index += 1 - self.root.ids.tabs.add_widget(Tab(title=f"{self.index} tab")) - - def remove_tab(self): - if self.index > 1: - self.index -= 1 - self.root.ids.tabs.remove_widget( - self.root.ids.tabs.get_tab_list()[-1] - ) - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.uix.button import MDFlatButton - from kivymd.uix.list import MDList - from kivymd.uix.scrollview import MDScrollView - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.uix.toolbar import MDTopAppBar - - - class Tab(MDScrollView, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - index = 0 - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDBoxLayout( - MDTopAppBar(title="Example Tabs"), - MDTabs(id="tabs"), - orientation="vertical", - ) - ) - - def on_start(self): - self.add_tab() - - def get_tab_list(self, *args): - '''Prints a list of tab objects.''' - - print(self.root.ids.tabs.get_tab_list()) - - def add_tab(self, *args): - self.index += 1 - self.root.ids.tabs.add_widget( - Tab( - MDList( - MDBoxLayout( - MDFlatButton( - text="ADD TAB", - on_release=self.add_tab, - ), - MDFlatButton( - text="REMOVE LAST TAB", - on_release=self.remove_tab, - ), - MDFlatButton( - text="GET TAB LIST", - on_release=self.get_tab_list, - ), - adaptive_height=True, - ), - ), - title=f"{self.index} tab", - ) - ) - - def remove_tab(self, *args): - if self.index > 1: - self.index -= 1 - self.root.ids.tabs.remove_widget( - self.root.ids.tabs.get_tab_list()[-1] - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-dynamic-managmant.gif - :align: center - -Use on_ref_press method ------------------------ - -You can use markup for the text of the tabs and use the ``on_ref_press`` -method accordingly: - -.. tabs:: - - .. tab:: Declarative KV and imperative python styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.font_definitions import fonts - from kivymd.uix.tab import MDTabsBase - from kivymd.icon_definitions import md_icons - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - on_ref_press: app.on_ref_press(*args) - - - - - MDIconButton: - id: icon - icon: app.icons[0] - icon_size: "48sp" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_start(self): - for name_tab in self.icons: - self.root.ids.tabs.add_widget( - Tab( - title=f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]{md_icons['close']}[/font][/ref] {name_tab}" - ) - ) - - def on_ref_press( - self, - instance_tabs, - instance_tab_label, - instance_tab, - instance_tab_bar, - instance_carousel, - ): - ''' - The method will be called when the ``on_ref_press`` event - occurs when you, for example, use markup text for tabs. - - :param instance_tabs: - :param instance_tab_label: - :param instance_tab: <__main__.Tab object> - :param instance_tab_bar: - :param instance_carousel: - ''' - - # Removes a tab by clicking on the close icon on the left. - for instance_tab in instance_carousel.slides: - if instance_tab.title == instance_tab_label.text: - instance_tabs.remove_widget(instance_tab_label) - break - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDIconButton - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.font_definitions import fonts - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.icon_definitions import md_icons - from kivymd.uix.toolbar import MDTopAppBar - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDBoxLayout( - MDTopAppBar(title="Example Tabs"), - MDTabs(id="tabs"), - orientation="vertical", - ) - ) - - def on_start(self): - self.root.ids.tabs.bind(on_ref_press=self.on_ref_press) - for name_tab in self.icons: - self.root.ids.tabs.add_widget( - Tab( - MDIconButton( - icon=self.icons[0], - icon_size="48sp", - pos_hint={"center_x": .5, "center_y": .5} - ), - title=( - f"[ref={name_tab}][font={fonts[-1]['fn_regular']}]" - f"{md_icons['close']}[/font][/ref] {name_tab}" - ), - ) - ) - - def on_ref_press( - self, - instance_tabs, - instance_tab_label, - instance_tab, - instance_tab_bar, - instance_carousel, - ): - ''' - The method will be called when the ``on_ref_press`` event - occurs when you, for example, use markup text for tabs. - - :param instance_tabs: - :param instance_tab_label: - :param instance_tab: <__main__.Tab object> - :param instance_tab_bar: - :param instance_carousel: - ''' - - # Removes a tab by clicking on the close icon on the left. - for instance_tab in instance_carousel.slides: - if instance_tab.title == instance_tab_label.text: - instance_tabs.remove_widget(instance_tab_label) - break - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tabs-on-ref-press.gif - :align: center - -Switching the tab by name -------------------------- - -.. tabs:: - - .. tab:: Declarative KV and imperative python styles - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.icon_definitions import md_icons - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "Example Tabs" - - MDTabs: - id: tabs - - - - - MDBoxLayout: - orientation: "vertical" - pos_hint: {"center_x": .5, "center_y": .5} - adaptive_size: True - spacing: dp(48) - - MDIconButton: - id: icon - icon: "arrow-right" - icon_size: "48sp" - on_release: app.switch_tab_by_name() - - MDIconButton: - id: icon2 - icon: "page-next" - icon_size: "48sp" - on_release: app.switch_tab_by_object() - ''' - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - self.iter_list_names = iter(list(self.icons)) - return Builder.load_string(KV) - - def on_start(self): - for name_tab in list(self.icons): - self.root.ids.tabs.add_widget(Tab(tab_label_text=name_tab)) - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - - def switch_tab_by_object(self): - try: - x = next(self.iter_list_objects) - print(f"Switch slide by object, next element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # reset the iterator an begin again. - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - self.switch_tab_by_object() - - def switch_tab_by_name(self): - '''Switching the tab by name.''' - - try: - x = next(self.iter_list_names) - print(f"Switch slide by name, next element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # Reset the iterator an begin again. - self.iter_list_names = iter(list(self.icons)) - self.switch_tab_by_name() - - - Example().run() - - .. tab:: Declarative python style - - .. code-block:: python - - from kivy.metrics import dp - - from kivymd.app import MDApp - from kivymd.icon_definitions import md_icons - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.button import MDIconButton - from kivymd.uix.floatlayout import MDFloatLayout - from kivymd.uix.tab import MDTabsBase, MDTabs - from kivymd.uix.toolbar import MDTopAppBar - - - class Tab(MDFloatLayout, MDTabsBase): - '''Class implementing content for a tab.''' - - - class Example(MDApp): - icons = list(md_icons.keys())[15:30] - - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - self.iter_list_names = iter(list(self.icons)) - return ( - MDBoxLayout( - MDTopAppBar(title="Example Tabs"), - MDTabs(id="tabs"), - orientation="vertical", - ) - ) - - def on_start(self): - for name_tab in list(self.icons): - self.root.ids.tabs.add_widget( - Tab( - MDBoxLayout( - MDIconButton( - id="icon", - icon="arrow-right", - icon_size="48sp", - on_release=self.switch_tab_by_name, - ), - MDIconButton( - id="icon2", - icon="arrow-left", - icon_size="48sp", - on_release=self.switch_tab_by_object, - ), - orientation="vertical", - pos_hint={"center_x": .5, "center_y": .5}, - adaptive_size=True, - spacing=dp(48), - ), - tab_label_text=name_tab, - ) - ) - - self.iter_list_objects = iter(list(self.root.ids.tabs.get_tab_list())) - - def switch_tab_by_object(self, *args): - try: - x = next(self.iter_list_objects) - print(f"Switch slide by object, next element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # reset the iterator an begin again. - self.iter_list_objects = iter( - list(self.root.ids.tabs.get_tab_list())) - self.switch_tab_by_object() - - def switch_tab_by_name(self, *args): - '''Switching the tab by name.''' - - try: - x = next(self.iter_list_names) - print(f"Switch slide by name, next element to show: [{x}]") - self.root.ids.tabs.switch_tab(x) - except StopIteration: - # Reset the iterator an begin again. - self.iter_list_names = iter(list(self.icons)) - self.switch_tab_by_name() - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/switching-tab-by-name.gif - :align: center + Example().run() """ -__all__ = ("MDTabs", "MDTabsBase") +from __future__ import annotations + +__all__ = ( + "MDTabsPrimary", + "MDTabsSecondary", + "MDTabsItem", + "MDTabsItemSecondary", + "MDTabsItemIcon", + "MDTabsItemText", + "MDTabsCarousel", + "MDTabsBadge", +) import os -from typing import Union +from kivy.uix.anchorlayout import AnchorLayout +from kivy.uix.carousel import Carousel +from kivy.uix.widget import Widget +from kivy.utils import boundary +from kivy.animation import Animation from kivy.clock import Clock -from kivy.graphics.texture import Texture from kivy.lang import Builder -from kivy.logger import Logger from kivy.metrics import dp from kivy.properties import ( - AliasProperty, - BooleanProperty, - BoundedNumericProperty, - ColorProperty, - ListProperty, - NumericProperty, ObjectProperty, - OptionProperty, + BooleanProperty, + ColorProperty, + NumericProperty, + AliasProperty, StringProperty, + VariableListProperty, ) -from kivy.uix.anchorlayout import AnchorLayout -from kivy.uix.behaviors import ToggleButtonBehavior +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout from kivy.uix.scrollview import ScrollView -from kivy.utils import boundary from kivymd import uix_path -from kivymd.font_definitions import fonts, theme_font_styles -from kivymd.icon_definitions import md_icons -from kivymd.theming import ThemableBehavior, ThemeManager +from kivymd.theming import ThemableBehavior +from kivymd.uix.badge import MDBadge from kivymd.uix.behaviors import ( DeclarativeBehavior, RectangularRippleBehavior, - SpecificBackgroundColorBehavior, + BackgroundColorBehavior, ) -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.card import MDCard -from kivymd.uix.carousel import MDCarousel -from kivymd.uix.label import MDLabel +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior +from kivymd.uix.label import MDLabel, MDIcon with open(os.path.join(uix_path, "tab", "tab.kv"), encoding="utf-8") as kv_file: Builder.load_string(kv_file.read()) -class MDTabsException(Exception): - pass +############################################################################### +# +# COMMON CLASSES +# +############################################################################### -class MDTabsLabel(ToggleButtonBehavior, RectangularRippleBehavior, MDLabel): - """This class it represent the label of each tab.""" - - text_color_normal = ColorProperty(None) - text_color_active = ColorProperty(None) - tab = ObjectProperty() - tab_bar = ObjectProperty() - font_name = StringProperty("Roboto") - - def __init__(self, **kwargs): - self.split_str = " ,-" - super().__init__(**kwargs) - self.max_lines = 2 - self.size_hint_x = None - self.size_hint_min_x = dp(90) - self.min_space = dp(98) - self.bind( - text=self._update_text_size, - ) - - def on_release(self) -> None: - try: - self.tab_bar.parent.dispatch( - "on_tab_switch", self.tab, self, self.text - ) - # If the label is selected load the relative tab from carousel. - if self.state == "down": - self.tab_bar.parent.carousel.load_slide(self.tab) - except KeyError: - pass - - def on_texture(self, instance_tabs_label, texture: Texture) -> None: - # Just save the minimum width of the label based of the content. - if texture: - max_width = dp(360) - min_width = dp(90) - if texture.width > max_width: - self.width = max_width - self.text_size = (max_width, None) - elif texture.width < min_width: - self.width = min_width - else: - self.width = texture.width - - def _update_text_size(self, *args): - if not self.tab_bar: - return - if self.tab_bar.parent.allow_stretch is True: - self.text_size = (None, None) - else: - self.width = self.tab_bar.parent.fixed_tab_label_width - self.text_size = (self.width, None) - Clock.schedule_once(self.tab_bar._label_request_indicator_update, 0) - - -class MDTabsBase: - """ - This class allow you to create a tab. - You must create a new class that inherits from MDTabsBase. - In this way you have total control over the views of your tabbed panel. +class MDTabsBadge(MDBadge): """ + Implements an badge for secondary tabs. - icon = StringProperty() - """ - This property will set the Tab's Label Icon. + .. versionadded:: 2.0.0 - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"]) - """ - This property sets the mode in wich the tab's title and icon are shown. - - :attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Lead'`. - """ - - title = StringProperty() - """ - This property will set the Name of the tab. - - .. note:: - As a side note. - - All tabs have set `markup = True`. - Thanks to this, you can use the kivy markup language to set a colorful - and fully customizable tabs titles. - - .. warning:: - The material design requires that every title label is written in - capital letters, because of this, the `string.upper()` will be applied - to it's contents. - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_is_capital = BooleanProperty(False) - """ - This value controls wether if the title property should be converted to - capital letters. - - :attr:`title_is_capital` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - tab_label_text = StringProperty() - """ - This property is the actual title's Label of the tab. - use the property :attr:`icon` and :attr:`title` to set this property - correctly. - - This property is kept public for specific and backward compatibility - purposes. - - :attr:`tab_label_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tab_label = ObjectProperty() - """ - It is the label object reference of the tab. - - :attr:`tab_label` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - def _get_label_font_style(self): - if self.tab_label: - return self.tab_label.font_style - - def _set_label_font_style(self, value): - if self.tab_label: - if value in theme_font_styles: - self.tab_label.font_style = value - else: - raise ValueError( - "tab_label_font_style:\n\t" - "font_style not found in theme_font_styles\n\t" - f"font_style = {value}" - ) - else: - Clock.schedule_once(lambda x: self._set_label_font_style(value)) - return True - - tab_label_font_style = AliasProperty( - _get_label_font_style, - _set_label_font_style, - cache=True, - ) - """ - :attr:`tab_label_font_style` is an :class:`~kivy.properties.AliasProperty` - that behavies similar to an :class:`~kivy.properties.OptionProperty`. - - This property's behavior allows the developer to use any new label style - registered to the app. - - This property will affect the Tab's Title Label widget. - """ - - def __init__(self, *args, **kwargs): - self.tab_label = MDTabsLabel(tab=self) - super().__init__(*args, **kwargs) - self.bind( - icon=self._update_text, - title=self._update_text, - title_icon_mode=self._update_text, - tab_label_text=self.update_label_text, - title_is_capital=self.update_label_text, - ) - Clock.schedule_once( - self._update_text - ) # this will ensure the text is correct - - def _update_text(self, *args): - # Ensures that the title is in capital letters. - if self.title and self.title_is_capital is True: - if self.title != self.title.upper(): - self.title = self.title.upper() - # Avoids event recursion. - return - # Add the icon. - if self.icon and self.icon in md_icons: - self.tab_label_text = f"[size=24sp][font={fonts[-1]['fn_regular']}]{md_icons[self.icon]}[/size][/font]" - if self.title: - self.tab_label_text = ( - self.tab_label_text - + (" " if self.title_icon_mode == "Lead" else "\n") - + self.title - ) - # Add the title. - else: - if self.icon: - Logger.error( - f"{self}: [UID] = [{self.uid}]:\n\t" - f"Icon '{self.icon}' not found in md_icons" - ) - if self.title: - self.tab_label_text = self.title - else: - if not self.tab_label_text: - raise ValueError( - f"{self}: [UID] = [{self.uid}]:\n\t" - "No valid Icon was found.\n\t" - "No valid Title was found.\n\t" - f"Icon\t= '{self.icon}'\n\t" - f"Title\t= '{self.title}'\n\t" - ) - - self.tab_label.padding = dp(16), 0 - self.update_label_text(None, self.tab_label_text) - - def update_label_text(self, instance_user_tab, text_tab: str) -> None: - self.tab_label.text = text_tab - - -class MDTabsMain(MDBoxLayout): - """ - This class is just a boxlayout that contain the carousel. - It allows you to have control over the carousel. + For more information, see in the + :class:`~kivymd.uix.badge.badge.MDBadge` class documentation. """ -class MDTabsCarousel(MDCarousel): +class MDTabsCarousel(Carousel): + """ + Implements a carousel for user-generated content. + + For more information, see in the + :class:`~kivy.uix.carousel.Carousel` class documentation. + """ + lock_swiping = BooleanProperty(False) """ If True - disable switching tabs by swipe. @@ -1201,7 +598,9 @@ class MDTabsCarousel(MDCarousel): and defaults to `False`. """ - def on_touch_move(self, touch): + _tabs = ObjectProperty() # MDTabsPrimary/MDTabsSecondary object + + def on_touch_move(self, touch) -> str | bool | None: if self.lock_swiping: # lock a swiping return if not self.touch_mode_change: @@ -1251,12 +650,18 @@ class MDTabsCarousel(MDCarousel): return True -class MDTabsScrollView(ScrollView): - """This class hacked version to fix scroll_x manual setting.""" +class MDTabsScrollView(BackgroundColorBehavior, ScrollView): + """ + Implements a scrollable list of tabs. + This class hacked version to fix scroll_x manual setting. - def goto( - self, scroll_x: Union[float, None], scroll_y: Union[float, None] - ) -> None: + For more information, see in the + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivy.uix.scrollview.ScrollView` + classes documentation. + """ + + def goto(self, scroll_x: float | None, scroll_y: float | None) -> None: """Update event value along with scroll_*.""" def _update(e, x): @@ -1272,10 +677,241 @@ class MDTabsScrollView(ScrollView): _update(self.effect_y, scroll_y) -class MDTabsBar(MDCard): +class MDTabsItemText(MDLabel): """ - This class is just a boxlayout that contains the scroll view for tabs. - It is also responsible for resizing the tab shortcut when necessary. + Implements an label for the :class:`~MDTabsItem` class. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + + .. versionadded:: 2.0.0 + """ + + _active = BooleanProperty(False) + + +class MDTabsItemIcon(MDIcon): + """ + Implements an icon for the :class:`~MDTabsItem` class. + + For more information, see in the + :class:`~kivymd.uix.label.label.MDIcon` class documentation. + + .. versionadded:: 2.0.0 + """ + + +class MDTabsItemBase( + DeclarativeBehavior, + BackgroundColorBehavior, + RectangularRippleBehavior, + ButtonBehavior, + ThemableBehavior, + StateLayerBehavior, +): + """ + Implements a base item with an icon and text. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.behaviors.ripple_behavior.RectangularRippleBehavior` and + :class:`~kivy.uix.behaviors.ButtonBehavior` and + :class:`~kivymd.theming.ThemableBehavior` + classes documentation. + """ + + active = BooleanProperty(False) + """ + Is the tab active. + + :attr:`active` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + _tabs = ObjectProperty() # MDTabsPrimary/MDTabsSecondary object + _tab_content = ObjectProperty() # Carousel slide (related content) object + + def on_release(self, *args) -> None: + """ + Fired when the button is released + (i.e. the touch/click that pressed the button goes away). + """ + + if self._tab_content: + self._tabs._tabs_carousel.load_slide(self._tab_content) + + self._tabs.update_indicator(instance=self) + self._tabs.dispatch("on_tab_switch", self, self._tab_content) + self._tabs._current_tab = self + self._tabs._current_related_content = self._tab_content + + +class MDTabsItem(MDTabsItemBase, BoxLayout): + """ + Implements a item with an icon and text for :class:`~MDTabsPrimary` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~MDTabsItemBase` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + """ + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, (MDTabsItemText, MDTabsItemIcon)): + if len(self.children) <= 1: + Clock.schedule_once(lambda x: self._set_width(widget)) + + def _set_width(self, widget): + def set_width(*args): + self.width = widget.texture_size[0] + widget.padding_x + 2 + + if not self._tabs.allow_stretch and isinstance(widget, MDTabsItemText): + Clock.schedule_once(set_width) + + super().add_widget(widget) + + +############################################################################### +# +# PRIMARY CLASSES +# +############################################################################### + + +class MDTabsPrimary(DeclarativeBehavior, ThemableBehavior, BoxLayout): + """ + Tabs primary class. + + .. versionchanged:: 2.0.0 + + Rename from `MDTabs` to `MDTabsPrimary` class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` + classes documentation. + + :Events: + `on_tab_switch` + Fired when switching tabs. + """ + + md_bg_color = ColorProperty(None) + """ + The background color of the widget. + + :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + label_only = BooleanProperty(False) + """ + Tabs with a label only or with an icon and a label. + + .. versionadded:: 2.0.0 + + :attr:`label_only` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + allow_stretch = BooleanProperty(True) + """ + Whether to stretch tabs to the width of the panel. + + :attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + + lock_swiping = BooleanProperty(False) + """ + If True - disable switching tabs by swipe. + + :attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + anim_duration = NumericProperty(0.2) + """ + Duration of the slide animation. + + :attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.2`. + """ + + indicator_anim = BooleanProperty(True) + """ + Tab indicator animation. If you want use animation set it to ``True``. + + .. versionchanged:: 2.0.0 + + Rename from `tab_indicator_anim` to `indicator_anim` attribute. + + :attr:`indicator_anim` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `True`. + """ + + indicator_radius = VariableListProperty([dp(2), dp(2), 0, 0], lenght=4) + """ + Radius of the tab indicator. + + .. versionadded:: 2.0.0 + + :attr:`indicator_radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[dp(2), dp(2), 0, 0]`. + """ + + indicator_height = NumericProperty("4dp") + """ + Height of the tab indicator. + + .. versionchanged:: 2.0.0 + + Rename from `tab_indicator_height` to `indicator_height` attribute. + + :attr:`indicator_height` is an :class:`~kivy.properties.NumericProperty` + and defaults to `'4dp'`. + """ + + indicator_duration = NumericProperty(0.5) + """ + The duration of the animation of the indicator movement when switching + tabs. + + .. versionadded:: 2.0.0 + + :attr:`indicator_duration` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.5`. + """ + + indicator_transition = StringProperty("out_expo") + """ + The transition name of animation of the indicator movement when switching + tabs. + + .. versionadded:: 2.0.0 + + :attr:`indicator_transition` is an :class:`~kivy.properties.StringProperty` + and defaults to `'out_expo'. + """ + + def get_last_scroll_x(self): + return self.ids.tab_scroll.scroll_x + + last_scroll_x = AliasProperty( + get_last_scroll_x, bind=("target",), cache=True + ) + """ + Is the carousel reference of the next tab/slide. + When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the + target tab/slide of the carousel. + + :attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`. """ target = ObjectProperty(None, allownone=True) @@ -1289,692 +925,468 @@ class MDTabsBar(MDCard): """ def get_rect_instruction(self): - canvas_instructions = self.layout.canvas.before.get_group( - "Indicator_line" + canvas_instructions = self.ids.container.canvas.before.get_group( + "md-tabs-rounded-rectangle" ) return canvas_instructions[0] indicator = AliasProperty(get_rect_instruction, cache=True) """ - It is the :class:`~kivy.graphics.vertex_instructions.RoundedRectangle` + It is the :class:`~kivy.graphics.vertex_instructions.SmoothRoundedRectangle` instruction reference of the tab indicator. :attr:`indicator` is an :class:`~kivy.properties.AliasProperty`. """ - def get_last_scroll_x(self): - return self.scrollview.scroll_x + _tabs_carousel = ObjectProperty() # MDTabsCarousel object + _current_tab = None # MDTabsItem object + _current_related_content = None # Carousel slide (related content) object + _do_releasing = True - last_scroll_x = AliasProperty( - get_last_scroll_x, bind=("target",), cache=True - ) - """ - Is the carousel reference of the next tab/slide. - When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the - target tab/slide of the carousel. + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.register_event_type("on_tab_switch") + self.register_event_type("on_slide_progress") + Clock.schedule_once(self._check_panel_height) + Clock.schedule_once(self._set_slides_attributes) - :attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def update_indicator( - self, x: Union[float, int], w: Union[float, int], radius=None - ) -> None: - # Update position and size of the indicator. - if self.parent.tab_indicator_type == "line-round": - self.parent._line_x = x - self.parent._line_width = w - self.parent._line_height = self.parent.tab_indicator_height - self.parent._line_radius = self.parent.tab_indicator_height / 2 - elif self.parent.tab_indicator_type == "line-rect": - self.parent._line_x = x - self.parent._line_width = w - self.parent._line_height = self.parent.tab_indicator_height + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, MDTabsCarousel): + self._tabs_carousel = widget + widget._tabs = self + widget.bind( + _offset=self.android_animation, index=self.on_carousel_index + ) + return super().add_widget(widget) + elif isinstance(widget, MDTabsItem) or ( + isinstance(self, MDTabsSecondary) + and isinstance(widget, MDTabsItemSecondary) + ): + widget._tabs = self + widget.bind(on_release=self.set_active_item) + self.ids.container.add_widget(widget) else: - self.indicator.pos = (x, 0) - self.indicator.size = (w, self.parent.tab_indicator_height) - if radius: - self.indicator.radius = radius + return super().add_widget(widget) + + def do_autoscroll_tabs(self, instance: MDTabsItem, value: float) -> None: + """ + Automatically scrolls the list of tabs when swiping the carousel + slide (related content). + + .. versionchanged:: 2.0.0 + + Rename from `tab_bar_autoscroll` to `do_autoscroll_tabs` method. + """ - def tab_bar_autoscroll(self, instance_tab_label: MDTabsLabel, step: float): - # Automatic scroll animation of the tab bar. bound_left = self.center_x - self.x - bound_right = self.layout.width - bound_left - dt = instance_tab_label.center_x - bound_left - sx, sy = self.scrollview.convert_distance_to_scroll(dt, 0) + bound_right = self.ids.container.width - bound_left + dt = instance.center_x - bound_left + sx, sy = self.ids.tab_scroll.convert_distance_to_scroll(dt, 0) lsx = self.last_scroll_x # ast scroll x of the tab bar scroll_is_late = lsx < sx # determine scroll direction - dst = abs(lsx - sx) * step # distance to run + dst = abs(lsx - sx) * value # distance to run) if not dst: return - if scroll_is_late and instance_tab_label.center_x > bound_left: + if scroll_is_late and instance.center_x > bound_left: x = lsx + dst - elif not scroll_is_late and instance_tab_label.center_x < bound_right: + elif not scroll_is_late and instance.center_x < bound_right: x = lsx - dst else: return + x = boundary(x, 0.0, 1.0) - self.scrollview.goto(x, None) + self.ids.tab_scroll.goto(x, None) def android_animation( - self, instance_carousel: MDTabsCarousel, offset: Union[float, int] - ): + self, instance: MDTabsCarousel, offset: float + ) -> None: + """Fired when swiping a carousel slide (related content).""" + + self.dispatch("on_slide_progress", instance, offset) + # Try to reproduce the android animation effect. - if offset != 0 and abs(offset) < instance_carousel.width: + if offset != 0 and abs(offset) < instance.width: forward = offset < 0 offset = abs(offset) - step = offset / float(instance_carousel.width) - indicator_animation = self.parent.tab_indicator_anim + step = offset / float(instance.width) skip_slide = ( - instance_carousel.slides[instance_carousel._skip_slide] - if instance_carousel._skip_slide is not None + instance.slides[instance._skip_slide] + if instance._skip_slide is not None else None ) next_slide = ( - instance_carousel.next_slide - if forward - else instance_carousel.previous_slide + instance.next_slide if forward else instance.previous_slide ) self.target = skip_slide if skip_slide else next_slide if not self.target: return - a = instance_carousel.current_slide.tab_label - b = self.target.tab_label - self.tab_bar_autoscroll(b, step) + a = instance.current_slide.tab_item + b = self.target.tab_item + self.do_autoscroll_tabs(b, step) + item_text_object = self._get_tab_item_text_icon_object() - # Avoids the animation if `indicator_animation` is True. - if indicator_animation is False: - return - gap_x = abs((a.x) - (b.x)) - gap_w = (b.width) - (a.width) - if forward: - x_step = a.x + (gap_x * step) - else: - x_step = a.x - gap_x * step - w_step = a.width + (gap_w * step) - self.update_indicator(x_step, w_step) + if item_text_object: + if self.__class__.__name__ == "MDTabsSecondary": + tab_text_width = a.width + else: + tab_text_width = item_text_object.texture_size[0] - def _label_request_indicator_update(self, *args): - widget = self.carousel.current_slide.tab_label - self.update_indicator(widget.x, widget.width) + if self.indicator_anim is False: + return + gap_x = abs(a.x - b.x) + if forward: + x_step = ( + a.x + + (a.width / 2 - tab_text_width / 2) + + dp(4) + + (gap_x * step) + ) + else: + x_step = ( + a.x + + (a.width / 2 - tab_text_width / 2) + + dp(4) + - gap_x * step + ) -class MDTabs( - DeclarativeBehavior, - ThemableBehavior, - SpecificBackgroundColorBehavior, - AnchorLayout, -): - """ - Tabs class. - You can use this class to create your own tabbed panel. + w_step = tab_text_width - ( + dp(8) if self.__class__.__name__ == "MDTabsPrimary" else 0 + ) + self.update_indicator(x_step, w_step) - For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior` and - :class:`~kivy.uix.anchorlayout.AnchorLayout` - classes documentation. - - :Events: - `on_tab_switch` - Called when switching tabs. - `on_slide_progress` - Called while the slide is scrolling. - `on_ref_press` - The method will be called when the ``on_ref_press`` event - occurs when you, for example, use markup text for tabs. - """ - - tab_bar_height = NumericProperty("48dp") - """ - Height of the tab bar. - - :attr:`tab_bar_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'48dp'`. - """ - - tab_padding = ListProperty([0, 0, 0, 0]) - """ - Padding of the tab bar. - - :attr:`tab_padding` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - tab_indicator_anim = BooleanProperty(False) - """ - Tab indicator animation. If you want use animation set it to ``True``. - - :attr:`tab_indicator_anim` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - tab_indicator_height = NumericProperty("2dp") - """ - Height of the tab indicator. - - :attr:`tab_indicator_height` is an :class:`~kivy.properties.NumericProperty` - and defaults to `'2dp'`. - """ - - tab_indicator_type = OptionProperty( - "line", options=["line", "fill", "round", "line-round", "line-rect"] - ) - """ - Type of tab indicator. Available options are: `'line'`, `'fill'`, - `'round'`, `'line-rect'` and `'line-round'`. - - :attr:`tab_indicator_type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'line'`. - """ - - tab_hint_x = BooleanProperty(False) - """ - This option affects the size of each child. if it's `True`, the size of - each tab will be ignored and will use the size available by the container. - - :attr:`tab_hint_x` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - anim_duration = NumericProperty(0.2) - """ - Duration of the slide animation. - - :attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - anim_threshold = BoundedNumericProperty( - 0.8, min=0.0, max=1.0, errorhandler=lambda x: 0.0 if x < 0.0 else 1.0 - ) - """ - Animation threshold allow you to change the tab indicator animation effect. - - :attr:`anim_threshold` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `0.8`. - """ - - allow_stretch = BooleanProperty(True) - """ - If `True`, the tab will update dynamically (if :attr:`tab_hint_x` is `True`) - to it's content width, and wrap any text if the widget is wider than `"360dp"`. - - If `False`, the tab won't update to it's maximum texture width. - this means that the `fixed_tab_label_width` will be used as the label - width. this will wrap any text inside to fit the fixed value. - - :attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - fixed_tab_label_width = NumericProperty("140dp") - """ - If :attr:`allow_stretch` is `False`, the class will set this value as the - width to all the tabs title label. - - :attr:`fixed_tab_label_width` is an :class:`~kivy.properties.NumericProperty` - and defaults to `140dp`. - """ - - background_color = ColorProperty(None) - """ - Background color of tabs in (r, g, b, a) or string format. - - :attr:`background_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - underline_color = ColorProperty([0, 0, 0, 0]) - """ - Underline color of tabs in (r, g, b, a) or string format. - - :attr:`underline_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - text_color_normal = ColorProperty(None) - """ - Text color in (r, g, b, a) or string format of the label when it is not selected. - - :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - text_color_active = ColorProperty(None) - """ - Text color in (r, g, b, a) or string format of the label when it is selected. - - :attr:`text_color_active` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - shadow_softness = NumericProperty(12) - """ - See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_softness` - attribute. - - .. versionadded:: 1.1.0 - - :attr:`shadow_softness` is an :class:`~kivy.properties.NumericProperty` - and defaults to `12`. - """ - - shadow_color = ColorProperty([0, 0, 0, 0.6]) - """ - See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_color` - attribute. - - .. versionadded:: 1.1.0 - - :attr:`shadow_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0.6]`. - """ - - shadow_offset = ListProperty((0, 0)) - """ - See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.shadow_offset` - attribute. - - .. versionadded:: 1.1.0 - - :attr:`shadow_offset` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0, 0]`. - """ - - elevation = NumericProperty(0) - """ - See :attr:`kivymd.uix.behaviors.CommonElevationBehavior.elevation` - attribute. - - :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0`. - """ - - indicator_color = ColorProperty(None) - """ - Color indicator in (r, g, b, a) or string format. - - :attr:`indicator_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - lock_swiping = BooleanProperty(False) - """ - If True - disable switching tabs by swipe. - - :attr:`lock_swiping` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - font_name = StringProperty("Roboto") - """ - Font name for tab text. - - :attr:`font_name` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - ripple_duration = NumericProperty(2) - """ - Ripple duration when long touching to tab. - - :attr:`ripple_duration` is an :class:`~kivy.properties.NumericProperty` - and defaults to `2`. - """ - - no_ripple_effect = BooleanProperty(True) - """ - Whether to use the ripple effect when tapping on a tab. - - :attr:`no_ripple_effect` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - title_icon_mode = OptionProperty("Lead", options=["Lead", "Top"]) - """ - This property sets the mode in wich the tab's title and icon are shown. - - :attr:`title_icon_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Lead'`. - """ - - force_title_icon_mode = BooleanProperty(True) - """ - If this property is se to `True`, it will force the class to update every - tab inside the scroll view to the current `title_icon_mode` - - :attr:`force_title_icon_mode` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.register_event_type("on_tab_switch") - self.register_event_type("on_ref_press") - self.register_event_type("on_slide_progress") - Clock.schedule_once(self._carousel_bind, 1) - self.theme_cls.bind( - primary_palette=self.update_icon_color, - theme_style=self.update_icon_color, - ) - self.bind( - force_title_icon_mode=self._parse_icon_mode, - title_icon_mode=self._parse_icon_mode, - ) - self.bind(tab_hint_x=self._update_tab_hint_x) - - def update_icon_color( - self, - instance_theme_manager: ThemeManager, - name_theme_style_name_palette: str, + def update_indicator( + self, x: float = 0.0, w: float = 0.0, instance: MDTabsItem = None ) -> None: - """ - Called when the app's color scheme or style has changed - (dark theme/light theme). - """ + """Update position and size of the indicator.""" - for tab_label in self.get_tab_list(): - if not self.text_color_normal: - tab_label.text_color_normal = self.theme_cls.text_color - if not self.text_color_active: - tab_label.text_color_active = self.specific_secondary_text_color + def update_indicator(*args): + indicator_pos = (0, 0) + indicator_size = (0, 0) - def switch_tab(self, name_tab: Union[MDTabsLabel, str], search_by="text"): - """ - This method switch between tabs - name_tab can be either a String or a :class:`~MDTabsBase`. + if self.__class__.__name__ == "MDTabsPrimary": + item_text_object = self._get_tab_item_text_icon_object() - `search_by` will look up through the properties of every tab. + if item_text_object: + tab_text_width = item_text_object.texture_size[0] + indicator_pos = ( + instance.x + + (instance.width / 2 - tab_text_width / 2) + + dp(4), + self.indicator.pos[1] + if not self._tabs_carousel + else self._tabs_carousel.height, + ) + indicator_size = ( + tab_text_width - dp(8), + self.indicator_height, + ) + elif self.__class__.__name__ == "MDTabsSecondary": + indicator_pos = (instance.x, self.indicator.pos[1]) + indicator_size = (instance.width, self.indicator_height) - If the value doesnt match, it will raise a ValueError. + Animation( + pos=indicator_pos, + size=indicator_size, + d=0 if not self.indicator_anim else self.indicator_duration, + t=self.indicator_transition, + ).start(self.indicator) - Search_by options: - text : will search by the raw text of the label (`tab_label_text`) - icon : will search by the `icon` property - title : will search by the `title` property - """ - - if isinstance(name_tab, str): - if search_by == "title": - for tab_instance in self.tab_bar.parent.carousel.slides: - if tab_instance.title_is_capital is True: - _name_tab = name_tab.upper() - else: - _name_tab = name_tab - if tab_instance.title == _name_tab: - self.carousel.load_slide(tab_instance) - return - # Search by icon. - elif search_by == "icon": - for tab_instance in self.tab_bar.parent.carousel.slides: - if tab_instance.icon == name_tab: - self.carousel.load_slide(tab_instance) - return - # Search by title. - else: - for tab_instance in self.tab_bar.parent.carousel.slides: - if tab_instance.tab_label_text == name_tab: - self.carousel.load_slide(tab_instance) - return - raise ValueError( - "switch_tab:\n\t" - "name_tab not found in the tab list\n\t" - f"search_by = {repr(search_by)} \n\t" - f"name_tab = {repr(name_tab)} \n\t" - ) + if not instance: + self.indicator.pos = (x, self.indicator.pos[1]) + self.indicator.size = (w, self.indicator_height) else: - self.carousel.load_slide(name_tab.tab) + Clock.schedule_once(update_indicator) - def get_tab_list(self) -> list: - """Returns a list of :class:`~MDTabsLabel` objects.""" + def switch_tab( + self, instance: MDTabsItem = None, text: str = "", icon: str = "" + ) -> None: + """Switches tabs by tab object/tab text/tab icon name.""" - return self.tab_bar.layout.children[::-1] + Clock.schedule_once( + lambda x: self._switch_tab(instance, text, icon), 0.8 + ) - def get_slides(self) -> list: - """Returns a list of user tab objects.""" + def set_active_item(self, item: MDTabsItem) -> None: + """Sets the active tab item.""" - return self.carousel.slides + for widget in self.ids.container.children: + if item is widget: + # Trying to switch an already active tab. + if widget.active and item.active: + break - def get_current_tab(self): + widget.active = not widget.active + + for widget_item in item.children: + if isinstance(widget_item, MDTabsItemText): + widget_item._active = widget.active + Animation( + text_color=self.theme_cls.primaryColor + if widget.active + else self.theme_cls.onSurfaceVariantColor, + d=0.2, + ).start(widget_item) + if isinstance(widget_item, MDTabsItemIcon): + widget_item._active = widget.active + Animation( + icon_color=self.theme_cls.primaryColor + if widget.active + else self.theme_cls.onSurfaceVariantColor, + d=0.2, + ).start(widget_item) + else: + widget.active = False + for widget_item in widget.children: + widget_item._active = widget.active + if isinstance(widget_item, MDTabsItemText): + Animation( + text_color=self.theme_cls.onSurfaceVariantColor, + d=0.2, + ).start(widget_item) + if isinstance(widget_item, MDTabsItemIcon): + Animation( + icon_color=self.theme_cls.onSurfaceVariantColor, + d=0.2, + ).start(widget_item) + + def get_tabs_list(self) -> list: + """ + Returns a list of :class:`~MDTabsItem` objects. + + .. versionchanged:: 2.0.0 + + Rename from `get_tab_list` to `get_tabs_list` method. + """ + + return self.ids.container.children + + def get_slides_list(self) -> list: + """ + Returns a list of user tab objects. + + .. versionchanged:: 2.0.0 + + Rename from `get_slides` to `get_slides_list` method. + """ + + if self._tabs_carousel: + return self._tabs_carousel.slides + + def get_current_tab(self) -> MDTabsItem: """ Returns current tab object. .. versionadded:: 1.0.0 """ - return self.carousel.current_slide + return self._current_tab - def add_widget(self, widget, index=0, canvas=None): - # You can add only subclass of MDTabsBase. - if not isinstance(widget, (MDTabsBase, MDTabsMain, MDTabsBar)): - raise ValueError( - f"MDTabs[{self.uid}].add_widget:\n\t" - "The widget provided is not a subclass of MDTabsBase." - ) - if len(self.children) >= 2: - try: - # FIXME: Can't set the value of the `no_ripple_effect` - # and `ripple_duration` properties for widget.tab_label. - widget.tab_label._no_ripple_effect = self.no_ripple_effect - widget.tab_label.ripple_duration_in_slow = self.ripple_duration - widget.tab_label.group = str(self) - widget.tab_label.tab_bar = self.tab_bar - widget.tab_label.font_name = self.font_name - widget.tab_label.text_color_normal = ( - self.text_color_normal - if self.text_color_normal - else self.specific_secondary_text_color - ) - widget.tab_label.text_color_active = ( - self.text_color_active - if self.text_color_active - else self.specific_text_color - ) - self.bind( - allow_stretch=widget.tab_label._update_text_size, - fixed_tab_label_width=widget.tab_label._update_text_size, - font_name=widget.tab_label.setter("font_name"), - text_color_active=widget.tab_label.setter( - "text_color_active" - ), - text_color_normal=widget.tab_label.setter( - "text_color_normal" - ), - ) - Clock.schedule_once(widget.tab_label._update_text_size, 0) - self.tab_bar.layout.add_widget(widget.tab_label) - self.carousel.add_widget(widget) - if self.force_title_icon_mode is True: - widget.title_icon_mode = self.title_icon_mode - Clock.schedule_once( - self.tab_bar._label_request_indicator_update, 0 - ) - return - except AttributeError: - pass - if isinstance(widget, (MDTabsMain, MDTabsBar)): - return super().add_widget(widget) - - def remove_widget(self, widget): - # You can remove only subclass of MDTabsLabel or MDTabsBase. - if not issubclass(widget.__class__, (MDTabsLabel, MDTabsBase)): - raise MDTabsException( - "MDTabs can remove only subclass of MDTabsLabel or MDTabsBase" - ) - # If the widget is an instance of MDTabsBase, then the widget is - # set as the widget's tab_label object. - if issubclass(widget.__class__, MDTabsBase): - slide = widget - title_label = widget.tab_label - else: - # We already got the label, so we set the slide reference. - slide = widget.tab - title_label = widget - # Set memory. - # Search object next tab. - # Clean all bindings to allow the widget to be collected. - self.unbind( - allow_stretch=title_label._update_text_size, - fixed_tab_label_width=title_label._update_text_size, - font_name=title_label.setter("font_name"), - text_color_active=title_label.setter("text_color_active"), - text_color_normal=title_label.setter("text_color_normal"), - ) - self.carousel.remove_widget(slide) - self.tab_bar.layout.remove_widget(title_label) - # Clean the references. - slide = None - title_label = None - widget = None - - def on_slide_progress(self, *args) -> None: + def get_current_related_content(self) -> Widget: """ - This event is deployed every available frame while the tab is scrolling. + Returns the carousel slide object (related content). + + .. versionadded:: 2.0.0 """ - def on_carousel_index(self, instance_tabs_carousel, index: int) -> None: - """ - Called when the Tab index have changed. - - This event is deployed by the built in carousel of the class. - """ - - # When the index of the carousel change, update tab indicator, - # select the current tab and reset threshold data. - if instance_tabs_carousel.current_slide: - current_tab_label = instance_tabs_carousel.current_slide.tab_label - if current_tab_label.state == "normal": - # current_tab_label._do_press() - current_tab_label.dispatch("on_release") - current_tab_label._release_group(self) - current_tab_label.state = "down" - - if self.tab_indicator_type == "round": - self.tab_indicator_height = self.tab_bar_height - if index == 0: - radius = [ - 0, - self.tab_bar_height / 2, - self.tab_bar_height / 2, - 0, - ] - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width, radius - ) - elif index == len(self.get_tab_list()) - 1: - radius = [ - self.tab_bar_height / 2, - 0, - 0, - self.tab_bar_height / 2, - ] - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width, radius - ) - else: - radius = [ - self.tab_bar_height / 2, - ] - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width, radius - ) - elif ( - self.tab_indicator_type == "fill" - or self.tab_indicator_type == "line-round" - or self.tab_indicator_type == "line-rect" - ): - self.tab_indicator_height = self.tab_bar_height - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width - ) - else: - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width - ) - - def on_ref_press(self, *args) -> None: - """ - This event will be launched every time the user press a markup enabled - label with a link or reference inside. - """ + return self._current_related_content def on_tab_switch(self, *args) -> None: """This event is launched every time the current tab is changed.""" - def on_size(self, instance_tab, size: list) -> None: - """Called when the application screen is resized.""" + def on_slide_progress(self, *args) -> None: + """ + This event is deployed every available frame while the tab is + scrolling. + """ - if self.carousel.current_slide: - self._update_indicator(self.carousel.current_slide.tab_label) + def on_carousel_index(self, instance: MDTabsCarousel, value: int) -> None: + """ + Fired when the Tab index have changed. + This event is deployed by the builtin carousel of the class. + """ - def _update_tab_hint_x(self, *args): - if not self.ids.layout.children: - return - if self.tab_hint_x is True: - self.fixed_tab_label_width = self.width // len( - self.ids.layout.children - ) - self.allow_stretch = False - else: - self.allow_stretch = True - - def _parse_icon_mode(self, *args): - if self.force_title_icon_mode is True: - for slide in self.carousel.slides: - slide.title_icon_mode = self.title_icon_mode - if self.title_icon_mode == "Top": - self.tab_bar_height = dp(72) - else: - self.tab_bar_height = dp(48) - - def _carousel_bind(self, interval): - self.carousel.bind(on_slide_progress=self._on_slide_progress) - - def _on_slide_progress(self, *args): - self.dispatch("on_slide_progress", args) - - def _update_indicator(self, current_tab_label): - def update_indicator(interval): - self.tab_bar.update_indicator( - current_tab_label.x, current_tab_label.width - ) - - if not current_tab_label: - current_tab_label = self.tab_bar.layout.children[-1] - Clock.schedule_once(update_indicator) - - def _update_padding(self, layout, *args): - if self.tab_hint_x is True: - layout.padding = [0, 0] - Clock.schedule_once(self._update_tab_hint_x) - return True - padding = [0, 0] - # FIXME: It's not entirely clear why the `padding = [dp (52), 0]` - # instruction is needed? This creates an extra 52px left padding and - # looks like a bug. This instruction was added by the contributors in - # previous commits and I have not yet figured out why this was done. - # This is more efficient than to use sum([layout.children]). - # width = layout.width - (layout.padding[0] * 2) - # Forces the padding of the tab_bar when the tab_bar is scrollable. - # if width > self.width: - # padding = [dp(52), 0] - # Set the new padding. - layout.padding = padding - # Update the indicator. - if self.carousel.current_slide: - self._update_indicator(self.carousel.current_slide.tab_label) + # When the index of the carousel change, update tab indicator, + # select the current tab and reset threshold data. + if instance.current_slide and hasattr( + instance.current_slide, "tab_item" + ): Clock.schedule_once( - lambda x: setattr( - self.carousel.current_slide.tab_label, "state", "down" - ), - -1, + lambda x: instance.current_slide.tab_item.dispatch("on_release") ) - return True + + def on_size(self, instance, size) -> None: + """Fired when the application screen size changes.""" + + width, height = size + number_tabs = len(self.ids.container.children) + + if self.allow_stretch: + for tab in self.ids.container.children: + tab.width = width / number_tabs + + if self._tabs_carousel: + Clock.schedule_once( + lambda x: self._tabs_carousel.current_slide.tab_item.dispatch( + "on_release" + ) + ) + + def _switch_tab( + self, instance: MDTabsItem = None, text: str = "", icon: str = "" + ): + def get_match(widget_to_compare, widget_to_compare_with, value, attr): + if isinstance(widget_to_compare, widget_to_compare_with): + if getattr(widget_to_compare, attr) == value: + return True + + def switch_by(by_attr, attr): + for tab_item in self.ids.container.children: + for child in tab_item.children: + if isinstance(child, MDTabsItemSecondaryContainer): + for w in child.children: + if get_match( + w, + MDTabsItemText + if by_attr == "text" + else MDTabsItemIcon, + attr, + by_attr, + ): + tab_item.dispatch("on_release") + break + else: + if get_match( + child, + MDTabsItemText + if by_attr == "text" + else MDTabsItemIcon, + attr, + by_attr, + ): + tab_item.dispatch("on_release") + break + + if instance and isinstance(instance, MDTabsItem): + instance.dispatch("on_release") + elif text: + switch_by("text", text) + elif icon: + switch_by("icon", icon) + + def _set_slides_attributes(self, *args): + if self._tabs_carousel: + tabs_item_list = self.ids.container.children.copy() + tabs_item_list.reverse() + + for i, tab_item in enumerate(tabs_item_list): + setattr(tab_item, "_tab_content", self._tabs_carousel.slides[i]) + setattr(self._tabs_carousel.slides[i], "tab_item", tab_item) + + def _get_tab_item_text_icon_object( + self, get_type="text" + ) -> MDTabsItemText | MDTabsItemIcon | None: + item_text_object = None + + for tab_item in self.ids.container.children: + if tab_item.active: + for child in tab_item.children: + if isinstance(child, MDTabsItemSecondaryContainer): + for w in child.children: + if isinstance( + w, + MDTabsItemText + if get_type == "text" + else MDTabsItemIcon, + ): + item_text_object = w + break + else: + if isinstance( + child, + MDTabsItemText + if get_type == "text" + else MDTabsItemIcon, + ): + item_text_object = child + break + return item_text_object + + def _check_panel_height(self, *args): + if self.label_only: + self.ids.tab_scroll.height = dp(48) + else: + self.ids.tab_scroll.height = dp(64) + + +############################################################################### +# +# SECONDARY CLASSES +# +############################################################################### + + +class MDTabsItemSecondaryContainer(BoxLayout): + """ + Implements a container for placing widgets for the + :class:`~MDTabsItemSecondary` class. + + For more information, see in the + :class:`~kivy.uix.boxlayout.BoxLayout` class documentation. + """ + + +class MDTabsItemSecondary(MDTabsItemBase, AnchorLayout): + """ + Implements a item with an icon and text for :class:`~MDTabsSecondary` + class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~MDTabsItemBase` and + :class:`~kivy.uix.anchorlayout.AnchorLayout` + classes documentation. + """ + + def add_widget(self, widget, *args, **kwargs): + if isinstance(widget, (MDTabsItemText, MDTabsItemIcon, MDTabsBadge)): + Clock.schedule_once( + lambda x: self.ids.box_container.add_widget(widget) + ) + else: + return super().add_widget(widget) + + +class MDTabsSecondary(MDTabsPrimary): + """ + Tabs secondary class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~MDTabsPrimary` class documentation. + """ + + indicator_radius = VariableListProperty(0, lenght=4) + """ + Radius of the tab indicator. + + :attr:`indicator_radius` is an :class:`~kivy.properties.VariableListProperty` + and defaults to `[0, 0, 0, 0]`. + """ + + indicator_height = NumericProperty("2dp") + """ + Height of the tab indicator. + + :attr:`indicator_height` is an :class:`~kivy.properties.NumericProperty` + and defaults to `'2dp'`. + """ + + def _check_panel_height(self, *args): + self.ids.tab_scroll.height = dp(48) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/taptargetview.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/taptargetview.py deleted file mode 100644 index 735de0d..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/taptargetview.py +++ /dev/null @@ -1,857 +0,0 @@ -""" -Components/TapTargetView -======================== - -.. seealso:: - - `TapTargetView, GitHub `_ - - `TapTargetView, Material archive `_ - -.. rubric:: Provide value and improve engagement by introducing users to new - features and functionality at relevant moments. - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-previous.gif - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.taptargetview import MDTapTargetView - - KV = ''' - Screen: - - MDFloatingActionButton: - id: button - icon: "plus" - pos: 10, 10 - on_release: app.tap_target_start() - ''' - - - class TapTargetViewDemo(MDApp): - def build(self): - screen = Builder.load_string(KV) - self.tap_target_view = MDTapTargetView( - widget=screen.ids.button, - title_text="This is an add button", - description_text="This is a description of the button", - widget_position="left_bottom", - ) - - return screen - - def tap_target_start(self): - if self.tap_target_view.state == "close": - self.tap_target_view.start() - else: - self.tap_target_view.stop() - - - TapTargetViewDemo().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-usage.gif - :align: center - -Widget position ---------------- - -Sets the position of the widget relative to the floating circle. - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="right", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="left", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-top.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="bottom", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-bottom.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="left_top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_top.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="right_top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_top.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="left_bottom", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-left_bottom.png - :align: center - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="right_bottom", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-right_bottom.png - :align: center - -If you use ``the widget_position = "center"`` parameter then you must -definitely specify the :attr:`~MDTapTargetView.title_position`. - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - widget_position="center", - title_position="left_top", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-position-center.png - :align: center - -Text options ------------- - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - title_text="Title text", - description_text="Description text", - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-text.png - :align: center - - -You can use the following options to control font size, color, and boldness: - -- :attr:`~MDTapTargetView.title_text_size` -- :attr:`~MDTapTargetView.title_text_color` -- :attr:`~MDTapTargetView.title_text_bold` -- :attr:`~MDTapTargetView.description_text_size` -- :attr:`~MDTapTargetView.description_text_color` -- :attr:`~MDTapTargetView.description_text_bold` - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - title_text="Title text", - title_text_size="36sp", - description_text="Description text", - description_text_color=[1, 0, 0, 1] - ) - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-text-option.png - :align: center - -But you can also use markup to set these values. - -.. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - title_text="[size=36]Title text[/size]", - description_text="[color=#ff0000ff]Description text[/color]", - ) - -Events control --------------- - -.. code-block:: python - - self.tap_target_view.bind(on_open=self.on_open, on_close=self.on_close) - -.. code-block:: python - - def on_open(self, instance_tap_target_view): - '''Called at the time of the start of the widget opening animation.''' - - print("Open", instance_tap_target_view) - - def on_close(self, instance_tap_target_view): - '''Called at the time of the start of the widget closed animation.''' - - print("Close", instance_tap_target_view) - -.. Note:: See other parameters in the :class:`~MDTapTargetView` class. -""" - -from kivy.animation import Animation -from kivy.event import EventDispatcher -from kivy.graphics import Color, Ellipse, Rectangle -from kivy.logger import Logger -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.label import Label - -from kivymd.theming import ThemableBehavior - - -class MDTapTargetView(ThemableBehavior, EventDispatcher): - """Rough try to mimic the working of Android's TapTargetView. - - :Events: - :attr:`on_open` - Called at the time of the start of the widget opening animation. - :attr:`on_close` - Called at the time of the start of the widget closed animation. - """ - - widget = ObjectProperty() - """ - Widget to add ``TapTargetView`` upon. - - :attr:`widget` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - outer_radius = NumericProperty(dp(200)) - """ - Radius for outer circle. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-radius.png - :align: center - - :attr:`outer_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(200)`. - """ - - outer_circle_color = ListProperty() - """ - Color for the outer circle in ``rgb`` format. - - .. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - outer_circle_color=(1, 0, 0) - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-outer-circle-color.png - :align: center - - :attr:`outer_circle_color` is an :class:`~kivy.properties.ListProperty` - and defaults to ``theme_cls.primary_color``. - """ - - outer_circle_alpha = NumericProperty(0.96) - """ - Alpha value for outer circle. - - :attr:`outer_circle_alpha` is an :class:`~kivy.properties.NumericProperty` - and defaults to `0.96`. - """ - - target_radius = NumericProperty(dp(45)) - """ - Radius for target circle. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-radius.png - :align: center - - :attr:`target_radius` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(45)`. - """ - - target_circle_color = ListProperty([1, 1, 1]) - """ - Color for target circle in ``rgb`` format. - - .. code-block:: python - - self.tap_target_view = MDTapTargetView( - ... - target_circle_color=(1, 0, 0) - ) - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tap-target-view-widget-target-circle-color.png - :align: center - - :attr:`target_circle_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1]`. - """ - - title_text = StringProperty() - """ - Title to be shown on the view. - - :attr:`title_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - title_text_size = NumericProperty(dp(25)) - """ - Text size for title. - - :attr:`title_text_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(25)`. - """ - - title_text_color = ListProperty([1, 1, 1, 1]) - """ - Text color for title. - - :attr:`title_text_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[1, 1, 1, 1]`. - """ - - title_text_bold = BooleanProperty(True) - """ - Whether title should be bold. - - :attr:`title_text_bold` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - description_text = StringProperty() - """ - Description to be shown below the title (keep it short). - - :attr:`description_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - description_text_size = NumericProperty(dp(20)) - """ - Text size for description text. - - :attr:`description_text_size` is an :class:`~kivy.properties.NumericProperty` - and defaults to `dp(20)`. - """ - - description_text_color = ListProperty([0.9, 0.9, 0.9, 1]) - """ - Text size for description text. - - :attr:`description_text_color` is an :class:`~kivy.properties.ListProperty` - and defaults to `[0.9, 0.9, 0.9, 1]`. - """ - - description_text_bold = BooleanProperty(False) - """ - Whether description should be bold. - - :attr:`description_text_bold` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - draw_shadow = BooleanProperty(False) - """ - Whether to show shadow. - - :attr:`draw_shadow` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - cancelable = BooleanProperty(False) - """ - Whether clicking outside the outer circle dismisses the view. - - :attr:`cancelable` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - widget_position = OptionProperty( - "left", - options=[ - "left", - "right", - "top", - "bottom", - "left_top", - "right_top", - "left_bottom", - "right_bottom", - "center", - ], - ) - """ - Sets the position of the widget on the :attr:`~outer_circle`. Available options are - `'left`', `'right`', `'top`', `'bottom`', `'left_top`', `'right_top`', - `'left_bottom`', `'right_bottom`', `'center`'. - - :attr:`widget_position` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'left'`. - """ - - title_position = OptionProperty( - "auto", - options=[ - "auto", - "left", - "right", - "top", - "bottom", - "left_top", - "right_top", - "left_bottom", - "right_bottom", - ], - ) - """ - Sets the position of :attr`~title_text` on the outer circle. Only works if - :attr`~widget_position` is set to `'center'`. In all other cases, it - calculates the :attr`~title_position` itself. - Must be set to other than `'auto`' when :attr`~widget_position` is set - to `'center`'. - - Available options are `'auto'`, `'left`', `'right`', `'top`', `'bottom`', - `'left_top`', `'right_top`', `'left_bottom`', `'right_bottom`', `'center`'. - - :attr:`title_position` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'auto'`. - """ - - stop_on_outer_touch = BooleanProperty(False) - """ - Whether clicking on outer circle stops the animation. - - :attr:`stop_on_outer_touch` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - stop_on_target_touch = BooleanProperty(True) - """ - Whether clicking on target circle should stop the animation. - - :attr:`stop_on_target_touch` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - state = OptionProperty("close", options=["close", "open"]) - """ - State of :class:`~MDTapTargetView`. - - :attr:`state` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'close'`. - """ - - _outer_radius = NumericProperty(0) - _target_radius = NumericProperty(0) - - __elevation = 0 - - def __init__(self, **kwargs): - self.ripple_max_dist = dp(90) - self.on_outer_radius(self, self.outer_radius) - self.on_target_radius(self, self.target_radius) - self.anim_ripple = None - - self.core_title_text = Label( - markup=True, size_hint=(None, None), bold=self.title_text_bold - ) - self.core_title_text.bind( - texture_size=self.core_title_text.setter("size") - ) - self.core_description_text = Label(markup=True, size_hint=(None, None)) - self.core_description_text.bind( - texture_size=self.core_description_text.setter("size") - ) - - super().__init__(**kwargs) - self.register_event_type("on_outer_touch") - self.register_event_type("on_target_touch") - self.register_event_type("on_outside_click") - self.register_event_type("on_open") - self.register_event_type("on_close") - - if not self.outer_circle_color: - self.outer_circle_color = self.theme_cls.primary_color[:-1] - - def start(self, *args): - """Starts widget opening animation.""" - - self._initialize() - self._animate_outer() - self.state = "open" - self.core_title_text.opacity = 1 - self.core_description_text.opacity = 1 - self.dispatch("on_open") - - elevation = getattr(self.widget, "elevation", None) - if elevation: - self.__elevation = elevation - self.widget.elevation = 0 - - def stop(self, *args): - """Starts widget close animation.""" - - # It needs a better implementation. - if self.anim_ripple is not None: - self.anim_ripple.unbind(on_complete=self._repeat_ripple) - self.core_title_text.opacity = 0 - self.core_description_text.opacity = 0 - anim = Animation( - d=0.15, - t="in_cubic", - **dict( - zip( - ["_outer_radius", "_target_radius", "target_ripple_radius"], - [0, 0, 0], - ) - ), - ) - anim.bind(on_complete=self._after_stop) - anim.start(self.widget) - - def on_open(self, *args): - """Called at the time of the start of the widget opening animation.""" - - def on_close(self, *args): - """Called at the time of the start of the widget closed animation.""" - - def on_draw_shadow(self, instance, value): - Logger.warning( - "The shadow adding method will be implemented in future versions" - ) - - def on_description_text(self, instance, value): - self.core_description_text.text = value - - def on_description_text_size(self, instance, value): - self.core_description_text.font_size = value - - def on_description_text_bold(self, instance, value): - self.core_description_text.bold = value - - def on_title_text(self, instance, value): - self.core_title_text.text = value - - def on_title_text_size(self, instance, value): - self.core_title_text.font_size = value - - def on_title_text_bold(self, instance, value): - self.core_title_text.bold = value - - def on_outer_radius(self, instance, value): - self._outer_radius = self.outer_radius * 2 - - def on_target_radius(self, instance, value): - self._target_radius = self.target_radius * 2 - - def on_target_touch(self): - if self.stop_on_target_touch: - self.stop() - - def on_outer_touch(self): - if self.stop_on_outer_touch: - self.stop() - - def on_outside_click(self): - if self.cancelable: - self.stop() - - def _initialize(self): - setattr(self.widget, "_outer_radius", 0) - setattr(self.widget, "_target_radius", 0) - setattr(self.widget, "target_ripple_radius", 0) - setattr(self.widget, "target_ripple_alpha", 0) - - # Bind some function on widget event when this function is called - # instead of when the class itself is initialized to prevent all - # widgets of all instances to get bind at once and start messing up. - self.widget.bind(on_touch_down=self._some_func) - - def _draw_canvas(self): - _pos = self._ttv_pos() - self.widget.canvas.before.remove_group("ttv_group") - - with self.widget.canvas.before: - # Outer circle. - Color( - *self.outer_circle_color, - self.outer_circle_alpha, - group="ttv_group", - ) - _rad1 = self.widget._outer_radius - Ellipse(size=(_rad1, _rad1), pos=_pos[0], group="ttv_group") - - # Title text. - Color(*self.title_text_color, group="ttv_group") - Rectangle( - size=self.core_title_text.texture.size, - texture=self.core_title_text.texture, - pos=_pos[1], - group="ttv_group", - ) - - # Description text. - Color(*self.description_text_color, group="ttv_group") - Rectangle( - size=self.core_description_text.texture.size, - texture=self.core_description_text.texture, - pos=( - _pos[1][0], - _pos[1][1] - self.core_description_text.size[1] - 5, - ), - group="ttv_group", - ) - - # Target circle. - Color(*self.target_circle_color, group="ttv_group") - _rad2 = self.widget._target_radius - Ellipse( - size=(_rad2, _rad2), - pos=( - self.widget.x - (_rad2 / 2 - self.widget.size[0] / 2), - self.widget.y - (_rad2 / 2 - self.widget.size[0] / 2), - ), - group="ttv_group", - ) - - # Target ripple. - Color( - *self.target_circle_color, - self.widget.target_ripple_alpha, - group="ttv_group", - ) - _rad3 = self.widget.target_ripple_radius - Ellipse( - size=(_rad3, _rad3), - pos=( - self.widget.x - (_rad3 / 2 - self.widget.size[0] / 2), - self.widget.y - (_rad3 / 2 - self.widget.size[0] / 2), - ), - group="ttv_group", - ) - - def _after_stop(self, *args): - self.widget.canvas.before.remove_group("ttv_group") - args[0].stop_all(self.widget) - - elevation = getattr(self.widget, "elevation", None) - if elevation: - self.widget.elevation = self.__elevation - - self.dispatch("on_close") - - # Don't forget to unbind the function or it'll mess - # up with other next bindings. - self.widget.unbind(on_touch_down=self._some_func) - self.state = "close" - - def _fix_elev(self): - with self.widget.canvas.before: - Color(a=self.widget._soft_shadow_a) - Rectangle( - texture=self.widget._soft_shadow_texture, - size=self.widget._soft_shadow_size, - pos=self.widget._soft_shadow_pos, - ) - Color(a=self.widget._hard_shadow_a) - Rectangle( - texture=self.widget._hard_shadow_texture, - size=self.widget._hard_shadow_size, - pos=self.widget._hard_shadow_pos, - ) - Color(a=1) - - def _animate_outer(self): - anim = Animation( - d=0.2, - t="out_cubic", - **dict( - zip( - ["_outer_radius", "_target_radius"], - [self._outer_radius, self._target_radius], - ) - ), - ) - anim.cancel_all(self.widget) - anim.bind(on_progress=lambda x, y, z: self._draw_canvas()) - anim.bind(on_complete=self._animate_ripple) - anim.start(self.widget) - setattr(self.widget, "target_ripple_radius", self._target_radius) - setattr(self.widget, "target_ripple_alpha", 1) - - def _animate_ripple(self, *args): - self.anim_ripple = Animation( - d=1, - t="in_cubic", - target_ripple_radius=self._target_radius + self.ripple_max_dist, - target_ripple_alpha=0, - ) - self.anim_ripple.stop_all(self.widget) - self.anim_ripple.bind(on_progress=lambda x, y, z: self._draw_canvas()) - self.anim_ripple.bind(on_complete=self._repeat_ripple) - self.anim_ripple.start(self.widget) - - def _repeat_ripple(self, *args): - setattr(self.widget, "target_ripple_radius", self._target_radius) - setattr(self.widget, "target_ripple_alpha", 1) - self._animate_ripple() - - def _some_func(self, wid, touch): - """ - This function decides which one to dispatch based on the touch - position. - """ - - if self._check_pos_target(touch.pos): - self.dispatch("on_target_touch") - elif self._check_pos_outer(touch.pos): - self.dispatch("on_outer_touch") - else: - self.dispatch("on_outside_click") - - def _check_pos_outer(self, pos): - """ - Checks if a given `pos` coordinate is within the :attr:`~outer_radius`. - """ - - cx = self.circ_pos[0] + self._outer_radius / 2 - cy = self.circ_pos[1] + self._outer_radius / 2 - r = self._outer_radius / 2 - h, k = pos - - lhs = (cx - h) ** 2 + (cy - k) ** 2 - rhs = r**2 - if lhs <= rhs: - return True - return False - - def _check_pos_target(self, pos): - """ - Checks if a given `pos` coordinate is within the - :attr:`~target_radius`. - """ - - cx = self.widget.pos[0] + self.widget.width / 2 - cy = self.widget.pos[1] + self.widget.height / 2 - r = self._target_radius / 2 - h, k = pos - - lhs = (cx - h) ** 2 + (cy - k) ** 2 - rhs = r**2 - if lhs <= rhs: - return True - return False - - def _ttv_pos(self): - """ - Calculates the `pos` value for outer circle and text - based on the position provided. - - :returns: A tuple containing pos for the circle and text. - """ - - _rad1 = self.widget._outer_radius - _center_x = self.widget.x - (_rad1 / 2 - self.widget.size[0] / 2) - _center_y = self.widget.y - (_rad1 / 2 - self.widget.size[0] / 2) - - if self.widget_position == "left": - circ_pos = (_center_x + _rad1 / 3, _center_y) - title_pos = (_center_x + _rad1 / 1.4, _center_y + _rad1 / 1.4) - elif self.widget_position == "right": - circ_pos = (_center_x - _rad1 / 3, _center_y) - title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 1.4) - elif self.widget_position == "top": - circ_pos = (_center_x, _center_y - _rad1 / 3) - title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4) - elif self.widget_position == "bottom": - circ_pos = (_center_x, _center_y + _rad1 / 3) - title_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 1.2) - # Corner ones need to be at a little smaller distance - # than edge ones that's why _rad1/4. - elif self.widget_position == "left_top": - circ_pos = (_center_x + _rad1 / 4, _center_y - _rad1 / 4) - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 4) - elif self.widget_position == "right_top": - circ_pos = (_center_x - _rad1 / 4, _center_y - _rad1 / 4) - title_pos = (_center_x - _rad1 / 10, _center_y + _rad1 / 4) - elif self.widget_position == "left_bottom": - circ_pos = (_center_x + _rad1 / 4, _center_y + _rad1 / 4) - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.2) - elif self.widget_position == "right_bottom": - circ_pos = (_center_x - _rad1 / 4, _center_y + _rad1 / 4) - title_pos = (_center_x, _center_y + _rad1 / 1.2) - else: - # Center. - circ_pos = (_center_x, _center_y) - - if self.title_position == "auto": - raise ValueError( - "widget_position='center' requires title_position to be set." - ) - elif self.title_position == "left": - title_pos = (_center_x + _rad1 / 10, _center_y + _rad1 / 2) - elif self.title_position == "right": - title_pos = (_center_x + _rad1 / 1.6, _center_y + _rad1 / 2) - elif self.title_position == "top": - title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 1.3) - elif self.title_position == "bottom": - title_pos = (_center_x + _rad1 / 2.5, _center_y + _rad1 / 4) - elif self.title_position == "left_top": - title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 1.4) - elif self.title_position == "right_top": - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 1.3) - elif self.title_position == "left_bottom": - title_pos = (_center_x + _rad1 / 8, _center_y + _rad1 / 4) - elif self.title_position == "right_bottom": - title_pos = (_center_x + _rad1 / 2, _center_y + _rad1 / 3.5) - else: - raise ValueError( - f"'{self.title_position}'" - f"is not a valid value for title_position" - ) - - self.circ_pos = circ_pos - return circ_pos, title_pos diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/__init__.py deleted file mode 100644 index 0e627d7..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Templates -========= - -Base classes for controlling the scale, rotation of the widget, etc. -""" - -from .rotatewidget import RotateWidget -from .scalewidget import ScaleWidget -from .stencilwidget import StencilWidget diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 0272844..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__init__.py deleted file mode 100644 index 1fb518f..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .rotatewidget import RotateWidget diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index ed752c3..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__pycache__/rotatewidget.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__pycache__/rotatewidget.cpython-311.pyc deleted file mode 100644 index 2eb04b6..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/__pycache__/rotatewidget.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/rotatewidget.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/rotatewidget.py deleted file mode 100644 index 9416a5f..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/rotatewidget/rotatewidget.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Templates/RotateWidget -====================== - -.. deprecated:: 1.0.0 - -.. note:: `RotateWidget` class has been deprecated. Please use - `RotateBahavior `_ - class instead. -""" - -__all__ = ("RotateWidget",) - -from kivy import Logger - -from kivymd.uix.behaviors import RotateBehavior - - -class RotateWidget(RotateBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~kivymd.uix.behaviors.rotate_behavior.RotateBehavior` - class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `RotateWidget` class has been deprecated. " - "Use the `RotateBehavior` class instead." - ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__init__.py deleted file mode 100644 index 45d304d..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .scalewidget import ScaleWidget diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 09e0b00..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__pycache__/scalewidget.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__pycache__/scalewidget.cpython-311.pyc deleted file mode 100644 index f4be6d1..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/__pycache__/scalewidget.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/scalewidget.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/scalewidget.py deleted file mode 100644 index 3fe6985..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/scalewidget/scalewidget.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Templates/ScaleWidget -===================== - -.. deprecated:: 1.1.0 - -Base class for controlling the scale of the widget. - -.. note:: `ScaleWidget` class has been deprecated. Please use - `ScaleBehavior `_ - class instead. -""" - -__all__ = ("ScaleWidget",) - -from kivy import Logger - -from kivymd.uix.behaviors import ScaleBehavior - - -class ScaleWidget(ScaleBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` - class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `ScaleWidget` class has been deprecated. " - "Use the `ScaleBehavior` class instead." - ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__init__.py deleted file mode 100644 index a249f51..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .stencilwidget import StencilWidget diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 37f9775..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__pycache__/stencilwidget.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__pycache__/stencilwidget.cpython-311.pyc deleted file mode 100644 index 8eb359f..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/__pycache__/stencilwidget.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/stencilwidget.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/stencilwidget.py deleted file mode 100644 index a231588..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/templates/stencilwidget/stencilwidget.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Templates/StencilWidget -======================= - -.. deprecated:: 1.1.0 - -Base class for controlling the stencil instructions of the widget. - -.. note:: `StencilWidget` class has been deprecated. Please use - `StencilBehavior `_ - class instead. -""" - -__all__ = ("StencilWidget",) - -from kivy import Logger - -from kivymd.uix.behaviors import StencilBehavior - - -class StencilWidget(StencilBehavior): - """ - .. deprecated:: 1.1.0 - Use :class:`~kivymd.uix.behaviors.scale_behavior.StencilBehavior` - class instead. - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - Logger.warning( - "KivyMD: " - "The `StencilWidget` class has been deprecated. " - "Use the `StencilBehavior` class instead." - ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__init__.py index d3786dd..cb6b57a 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__init__.py @@ -1,2 +1,9 @@ # NOQA F401 -from .textfield import MDTextField, MDTextFieldRect +from .textfield import ( + MDTextField, + MDTextFieldHelperText, + MDTextFieldMaxLengthText, + MDTextFieldHintText, + MDTextFieldLeadingIcon, + MDTextFieldTrailingIcon, +) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/__init__.cpython-311.pyc index 89efbba..fd96d89 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/textfield.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/textfield.cpython-311.pyc index 1861e7e..e9bc82f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/textfield.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/__pycache__/textfield.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.kv index 1ed7597..a85b26e 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.kv @@ -1,317 +1,408 @@ +#:import theme_font_styles kivymd.font_definitions.theme_font_styles + + input_filter: self.field_filter do_backspace: self.do_backspace canvas.before: Clear - - # "round" mode. - Color: - group: "round-color" - rgba: self._fill_color if self.mode == "round" else (0, 0, 0, 0) - Ellipse: - angle_start: 180 - angle_end: 360 - pos: self.x - self.height / 2 + dp(18), self.y - size: self.height, self.height - Ellipse: - angle_start: 360 - angle_end: 540 - pos: (self.width - dp(18)) + self.x - self.height / 2.0, self.y - size: self.height, self.height - Rectangle: - pos: self.x + dp(14), self.y - size: self.width - dp(28), self.height - - Color: - rgba: - ( \ - (self.line_color_focus if not self.error else self.error_color) \ - if self.focus else ( \ - self.theme_cls.disabled_hint_text_color \ - if not self.line_color_normal else \ - self.line_color_normal) \ - ) \ - if self.mode == "round" else \ - (0, 0, 0, 0) - SmoothLine: - width: dp(1) - rounded_rectangle: - self.x, \ - self.y, \ - self.width, \ - self.height, \ - self.height / 2 - - # "fill" mode. + # Filled mode. Color: group: "fill-color" - rgba: self._fill_color if self.mode == "fill" else (0, 0, 0, 0) + rgba: + ( \ + ( \ + self.theme_cls.surfaceVariantColor \ + if self.theme_bg_color == "Primary" else + ( \ + self.fill_color_normal \ + if self.fill_color_normal else \ + self.theme_cls.surfaceVariantColor \ + ) \ + ) \ + if not self.focus else \ + ( \ + self.theme_cls.surfaceVariantColor \ + if self.theme_bg_color == "Primary" else + ( \ + self.fill_color_focus \ + if self.fill_color_focus else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + ) \ + ) \ + if self.mode == "filled" else self.theme_cls.transparentColor RoundedRectangle: + group: "fill-color-rounded-rectangle" pos: self.x, self.y size: self.width, self.height - radius: self.radius + radius: self.radius[0], self.radius[1], 0, 0 - # Static underline texture. + # Active indicator. Color: - group: "static-underline-color" + group: "active-indicator-color" rgba: - (self._line_color_normal \ - if self.line_color_normal else self.theme_cls.divider_color) \ - if self.mode == "line" else (0, 0, 0, 0) + ( \ + ( \ + ( \ + ( \ + self.theme_cls.onSurfaceVariantColor \ + if self.theme_line_color == "Primary" else \ + ( \ + self.line_color_normal \ + if self.line_color_normal else \ + self.theme_cls.onSurfaceVariantColor \ + ) \ + ) \ + if not self.focus else \ + ( \ + self.theme_cls.primaryColor \ + if self.theme_line_color == "Primary" else \ + ( \ + self.line_color_focus \ + if self.line_color_focus else \ + self.theme_cls.primaryColor \ + ) \ + ) \ + ) \ + if not self.error else self._get_error_color() + ) \ + if not self.disabled else self.theme_cls.disabledTextColor \ + ) \ + if self.mode == "filled" else self.theme_cls.transparentColor Line: - points: self.x, self.y + dp(16), self.x + self.width, self.y + dp(16) - width: 1 - dash_length: dp(3) - dash_offset: 2 if self.disabled else 0 - - # Active underline (on focus) texture. - Color: - group: "active-underline-color" - rgba: - self._line_color_focus \ - if self.mode in ("line", "fill") and self.active_line \ - else (0, 0, 0, 0) - Rectangle: - size: self._underline_width, dp(1) - pos: - self.center_x - (self._underline_width / 2), \ - self.y + (dp(16) if self.mode != "fill" else 0) + width: self._indicator_height + points: + self.x + dp(1 if self.focus else 0), \ + self.y, \ + self.x - dp(1 if self.focus else 0) + self.width, \ + self.y # Helper text texture. Color: group: "helper-text-color" - rgba: - self.theme_cls.disabled_hint_text_color \ - if self.disabled else \ - self._helper_text_color - Rectangle: - texture: self._helper_text_label.texture - size: self._helper_text_label.texture_size - pos: - self.x + (dp(16) if self.mode == "fill" else \ - (0 if self.mode not in ("round", "rectangle") else dp(12))), \ - self.y + (dp(-18) if self.mode in ("fill", "rectangle", "round") else dp(-2)) - - # Right/left icon texture. - Color: - group: "right-left-icons-color" - rgba: - self.theme_cls.disabled_hint_text_color \ - if self.disabled else \ - (self._icon_right_color if self.icon_right else self._icon_left_color) Rectangle: texture: - self._icon_right_label.texture if self.icon_right else self._icon_left_label.texture + self._helper_text_label.texture \ + if self._helper_text_label else \ + None size: - (0, 0) if (not self.icon_right and not self.icon_left) else \ - (self._icon_right_label.texture_size if self.icon_right else self._icon_left_label.texture_size) + self._helper_text_label.texture_size \ + if self._helper_text_label else \ + (0, 0) + pos: + self.x + (dp(16) if self.mode == "filled" else \ + (0 if self.mode == "filled" else dp(12))), \ + self.y + dp(-18) + + # Leading icon texture. + Color: + group: "leading-icons-color" + Rectangle: + texture: + self._leading_icon.texture if self._leading_icon else None + size: + self._leading_icon.texture_size \ + if self._leading_icon else \ + (0, 0) pos: ( \ - (self.width + self.x - (0 if self.mode != "round" else dp(4))) - \ - (self._icon_right_label.texture_size[1]) - dp(8), \ - self.center[1] - self._icon_right_label.texture_size[1] / 2 + ((dp(8) \ - if self.mode != "round" else 0) if self.mode != "fill" else 0) \ - if self.mode != "rectangle" else \ - self.center[1] - self._icon_right_label.texture_size[1] / 2 - dp(4) \ - ) \ - if self.icon_right else \ ( \ - self.x + ((dp((0 if self.mode != "round" else 12)) \ - if self.mode != "rectangle" else dp(12)) \ - if self.mode != "fill" else (dp(4) if not self.icon_left else dp(16))), \ + self.x + \ + ( \ + ( \ + 0 if self.mode != "outlined" else dp(12) \ + ) \ + if self.mode != "filled" else \ + ( \ + dp(4) if not self._leading_icon else dp(16) \ + ) \ + ), \ - self.center[1] - self._icon_left_label.texture_size[1] / 2 + (((dp(4) \ - if self.mode != "round" else 0) if self.mode not in ("rectangle", "fill") \ - else dp(8)) if self.mode != "fill" else 0) \ - if self.mode != "rectangle" else \ - self.center[1] - self._icon_left_label.texture_size[1] / 2 - dp(4) \ - ) + self.center_y - self._leading_icon.texture_size[1] / 2 \ + ) \ + ) \ + if self._leading_icon else (0, 0) + + # Trailing icon texture. + Color: + group: "trailing-icons-color" + Rectangle: + texture: + self._trailing_icon.texture if self._trailing_icon else None + size: + self._trailing_icon.texture_size \ + if self._trailing_icon else \ + (0, 0) + pos: + ( \ + (self.width + self.x) - \ + (self._trailing_icon.texture_size[1]) - dp(14), \ + self.center_y - self._trailing_icon.texture_size[1] / 2 \ + ) \ + if self._trailing_icon else (0, 0) # Max length texture. Color: group: "max-length-color" - rgba: - self.theme_cls.disabled_hint_text_color \ - if self.disabled else \ - self._max_length_text_color Rectangle: - texture: self._max_length_label.texture - size: self._max_length_label.texture_size + group: "max-length-rect" + texture: + self._max_length_label.texture \ + if self._max_length_label else \ + None + size: + self._max_length_label.texture_size \ + if self._max_length_label else \ + (0, 0) pos: - self.x + self.width - self._max_length_label.texture_size[0] - dp(12), \ - self.y - (dp(2) if self.mode == "line" else dp(18)) + ( \ + (self.x + self.width) \ + - (self._max_length_label.texture_size[0] + dp(16)), \ + self.y - dp(18) \ + ) \ + if self._max_length_label else (0, 0) # Cursor blink. Color: rgba: ( \ - (self.text_color_focus if not self.error else self.error_color) \ + ( \ + self.theme_cls.primaryColor \ + if not self.error else \ + self._get_error_color() \ + ) \ if self.focus \ - else self._text_color_normal \ + else self.theme_cls.primaryColor \ ) \ if self.focus and not self._cursor_blink \ else \ (0, 0, 0, 0) Rectangle: + group: "rectangle-cursor-blink" pos: (int(x) for x in self.cursor_pos) size: 1, -self.line_height - # "rectangle" mode + # Outlined mode. Color: group: "rectangle-color" rgba: ( \ - (self.line_color_focus if not self.error else self.error_color) \ + ( \ + ( \ + ( \ + self.theme_cls.primaryColor \ + if self.theme_line_color == "Primary" else \ + self.line_color_focus \ + if self.line_color_focus else \ + self.theme_cls.primaryColor \ + ) \ if self.focus else \ + ( \ + self.theme_cls.outlineColor \ + if self.theme_line_color == "Primary" else \ self.line_color_normal \ + if self.line_color_normal else \ + self.theme_cls.outlineColor \ ) \ - if self.mode == "rectangle" else \ - (0, 0, 0, 0) - SmoothLine: - width: dp(1) - rounded_rectangle: - self.x, \ - self.y, \ - self.width, \ - self.height - self._hint_text_label.texture_size[1] // 2, \ - root.radius[0] + ) \ + if not self.error else self._get_error_color() \ + ) \ + if not self.disabled else \ + app.theme_cls.onSurfaceColor[:-1] + \ + [self.text_field_opacity_value_disabled_line] + ) \ + if self.mode != "filled" else self.theme_cls.transparentColor - # The background color line of the widget on which the text field - # is placed (for background hint text texture). - Color: - rgba: - ( \ - ( \ - self.parent.md_bg_color \ - if hasattr(self.parent, "md_bg_color") \ - and self.parent.md_bg_color != [1, 1, 1, 0] else \ - self.theme_cls.bg_normal \ - ) \ - if self.focus else \ - ( \ - (0, 0, 0, 0) if not self.text else \ - ( \ - self.parent.md_bg_color \ - if hasattr(self.parent, "md_bg_color") \ - and self.parent.md_bg_color != [1, 1, 1, 0] else \ - self.theme_cls.bg_normal \ - ) \ - ) \ - ) \ - if self.mode == "rectangle" else \ - (0, 0, 0, 0) + # Top right corner. + # ------------------------------------------------------─╮ SmoothLine: - width: dp(2) + width: self._outline_height + circle: + self.x + self.width - self.radius[1], \ + self.y + self.height - self.radius[1], \ + self.radius[1], \ + 0, \ + 90 + + # Bottom corner. + # -----------------------------------------------------─╯ + SmoothLine: + width: self._outline_height + circle: + self.x + self.width - self.radius[2], \ + self.y + self.radius[2], \ + -self.radius[2], \ + 0, \ + -90 + + # Top left corner. + # ╭─------------------------------------------------------ + SmoothLine: + width: self._outline_height + circle: + self.x + self.radius[0], \ + self.y + self.height - self.radius[0], \ + -self.radius[0], \ + 180, \ + 90 + + # Bottom left corner. + # ╰─----------------------------------------------------- + SmoothLine: + width: self._outline_height + circle: + self.x + self.radius[3], \ + self.y + self.radius[3], \ + -self.radius[3], \ + 0, \ + 90 + + # Left vertical line. + # │ + # │ + # ╰─------------------------------------------------------ + SmoothLine: + width: self._outline_height points: - self.x + dp(10), \ - self.top - self._hint_text_label.texture_size[1] // 2, \ - self.x + dp(16) + self._hint_text_label.texture_size[0], \ - self.top - self._hint_text_label.texture_size[1] // 2 + self.x, \ + self.y + self.radius[3], \ + self.x, \ + self.y + (self.height - self.radius[0]) + + # Right vertical line. + # │ + # │ + # -----------------------------------------------------─╯ + SmoothLine: + width: self._outline_height + points: + self.x + self.width, \ + self.y + self.radius[2], \ + self.x + self.width, \ + self.y + (self.height - self.radius[1]) + + # Bottom horizontal line. + # ——————————————————————————————————————————————————————─╯ + SmoothLine: + width: self._outline_height + points: + self.x + self.radius[3], \ + self.y, \ + self.x + self.width - self.radius[2], \ + self.y + + # Top (left) part of the line. + # ╭─----------------------------------------------------- + SmoothLine: + width: self._outline_height + points: + self.x + self.radius[0], \ + self.y + self.height, \ + self.x + self._left_x_axis_pos, \ + self.y + self.height + + # Top (right) part of the line. + # ╭─-----------—————————————————————————————————————————─╮ + SmoothLine: + width: self._outline_height + points: + self.x + self._right_x_axis_pos, \ + self.y + self.height, \ + self.x + self.width - self.radius[1], \ + self.y + self.height # Text color. Color: group: "text-color" - rgba: - self.theme_cls.disabled_hint_text_color if self.disabled else \ - ( \ - self.text_color_focus if self.focus else self._text_color_normal - ) \ - if not self.error else self.error_color - - canvas.after: - # Hint text texture. - Color: - group: "hint-text-color" rgba: self.theme_cls.disabled_hint_text_color \ if self.disabled else \ - self._hint_text_color - Rectangle: - texture: self._hint_text_label.texture - size: self._hint_text_label.texture_size - pos: ( \ - self.x + ((dp(16) if not self.icon_left else dp(52)) \ - if self.mode == "fill" else ( \ - ((0 if self.mode != "round" else dp(12)) if self.mode != "rectangle" else dp(12)) \ - if not self.icon_left else \ - (dp(36 if self.mode != "round" else 42) if self.mode != "rectangle" else dp(42)))) \ - if not self.focus and not self.text else \ - self.x + ((dp(16) if self.mode != "round" else dp(36 if not self.icon_left else 42)) \ - if self.mode in ("fill", "rectangle", "round") and \ - self.icon_left else (0 if self.mode != "round" else dp(12))) + self._hint_x - ), \ - - self.y + self.height + (((dp(4) if self.mode != "round" else dp(10)) \ - if self.mode != "line" else \ - dp(-6)) if self.mode != "rectangle" else dp(-4)) - self._hint_y - - - font_name: "Roboto" if not self.font_name else self.font_name - foreground_color: self.theme_cls.text_color - bold: False - padding: - ( \ - ((0 if self.mode != "round" else "12dp") \ - if self.mode != "rectangle" else "12dp") \ - if not self.icon_left else \ - (("36dp" if self.mode != "round" else "42dp") \ - if self.mode != "rectangle" else "42dp") \ - ) \ - if self.mode != "fill" else \ - ("16dp" if not self.icon_left else "52dp"), \ - - "24dp" if self.mode != "round" else "8dp", \ - - ((0 if self.mode != "round" and not self.icon_left else dp(12)) \ - if self.mode != "rectangle" else "12dp") \ - if self.mode != "fill" and not self.icon_right else \ - ( \ - "14dp" \ - if not self.icon_right else \ - self._icon_right_label.texture_size[1] + (dp(20) \ - if self.mode != "round" else dp(24))), \ - - "8dp" if self.mode == "fill" else \ - (("22dp" if self.mode != "round" else "8dp") \ - if self.icon_left and self.mode != "rectangle" else \ - ("16dp" if self.mode in ("fill", "rectangle") else \ - "20dp" if self.mode != "round" else "8dp")) - multiline: False - size_hint_y: None - height: self.minimum_height - - - - size_hint_x: None - width: self.texture_size[0] - shorten: True - shorten_from: "right" - - - - on_focus: - self.anim_rect((self.x, self.y, self.right, self.y, self.right, \ - self.top, self.x, self.top, self.x, self.y), 1) if self.focus \ - else self.anim_rect((self.x - dp(60), self.y - dp(60), \ - self.right + dp(60), self.y - dp(60), - self.right + dp(60), self.top + dp(60), \ - self.x - dp(60), self.top + dp(60), \ - self.x - dp(60), self.y - dp(60)), 0) + self.text_color_focus \ + if self.text_color_focus and self.theme_text_color == "Custom" \ + else self.theme_cls.onSurfaceColor \ + ) \ + if self.focus else \ + ( \ + self.text_color_normal \ + if self.text_color_normal and self.theme_text_color == "Custom" \ + else self.theme_cls.onSurfaceVariantColor \ + ) + # Hint texture. canvas.after: Color: - group: "color" - rgba: self._primary_color - Line: - group: "rectangle" - width: dp(1.5) - points: - ( - self.x - dp(60), self.y - dp(60), - self.right + dp(60), self.y - dp(60), - self.right + dp(60), self.top + dp(60), - self.x - dp(60), self.top + dp(60), - self.x - dp(60), self.y - dp(60) - ) + group: "hint-text-color" + Rectangle: + group: "hint-text-rectangle" + texture: + self._hint_text_label.texture \ + if self._hint_text_label else \ + None + size: + self._hint_text_label.texture_size \ + if self._hint_text_label else \ + (0, 0) + pos: + ( \ + self.x + \ + ( \ + dp(16) \ + if not self._leading_icon else \ + self._leading_icon.texture_size[0] + dp(28) + self._hint_x \ + ), \ + + self.y + self.height \ + + (self._hint_text_label.texture_size[1] / 2) \ + - (self.height / 2) \ + - self._hint_y \ + ) \ + if self._hint_text_label else (0, 0) + + bold: False + font_name: theme_font_styles[self.font_style][self.role]["font-name"] + font_size: theme_font_styles[self.font_style][self.role]["font-size"] + padding: + ( \ + dp(16) if not self._leading_icon else dp(42) \ + if self.mode != "filled" else \ + (dp(16) if not self._leading_icon else dp(52)), \ + + (self.height / 2.0 - (self.line_height / 2.0) * len(self._lines)) \ + + dp(8 if self.mode == "filled" else 0), \ + + dp(16) \ + if not self._trailing_icon else \ + self._trailing_icon.texture_size[0] + dp(28), \ + + 0 \ + ) + multiline: False + size_hint_y: None + height: dp(56) if not self.multiline else (dp(10) + self.minimum_height) + + + + size_hint: None, None + size: "20dp", "20dp" + size_hint_x: None + width: self.texture_size[0] + theme_text_color: "Custom" + + + + role: "large" + theme_font_size: "Custom" + + + + size_hint_x: None + width: self.texture_size[0] + adaptive_width: True + shorten: True + shorten_from: "right" + font_style: "Body" + role: "small" + theme_text_color: "Custom" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.py index a351580..c078989 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/textfield/textfield.py @@ -1,295 +1,251 @@ """ -Components/TextField -==================== +Components/Text fields +====================== .. seealso:: - `Material Design spec, Text fields `_ + `Material Design spec, Text fields `_ -.. rubric:: Text fields let users enter and edit text. +.. rubric:: Text fields let users enter text into a UI. .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields.png :align: center -`KivyMD` provides the following field classes for use: +- Make sure text fields look interactive +- Two types: filled and outlined +- The text field’s state (blank, with input, error, etc) should be visible at a glance +- Keep labels and error messages brief and easy to act on +- Text fields commonly appear in forms and dialogs -- MDTextField_ -- MDTextFieldRect_ +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/available-fields.png + :align: center -.. Note:: :class:`~MDTextField` inherited from - :class:`~kivy.uix.textinput.TextInput`. Therefore, most parameters and all - events of the :class:`~kivy.uix.textinput.TextInput` class are also - available in the :class:`~MDTextField` class. +1. Filled text field +2. Outlined text field -.. MDTextField: -MDTextField +Usage +----- + +.. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldLeadingIcon: + icon: "magnify" + + MDTextFieldHintText: + text: "Hint text" + + MDTextFieldHelperText: + text: "Helper text" + mode: "persistent" + + MDTextFieldTrailingIcon: + icon: "information" + + MDTextFieldMaxLengthText: + max_text_length: 10 + +Anatomy +------- + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-anatomy.png + :align: center + +Available types of text fields +============================== + +Filled mode ----------- -:class:`~MDTextField` can be with helper text and without. +.. code-block:: kv -Without helper text mode ------------------------- + MDTextField: + mode: "filled" + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-filled-mode.png + :align: center + +Outlined mode +------------- .. code-block:: kv MDTextField: - hint_text: "No helper text" + mode: "outlined" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-no-helper-mode.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-outlined-mode.png :align: center -Helper text mode on ``on_focus`` event --------------------------------------- +Example +------- -.. code-block:: kv +.. tabs:: - MDTextField: - hint_text: "Helper text on focus" - helper_text: "This will disappear when you click off" - helper_text_mode: "on_focus" + .. tab:: Declarative KV style -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-on-focus.gif - :align: center + .. code-block:: python -Persistent helper text mode ---------------------------- + from kivy.lang import Builder -.. code-block:: kv + from kivymd.app import MDApp - MDTextField: - hint_text: "Persistent helper text" - helper_text: "Text is always here" - helper_text_mode: "persistent" + KV = ''' + MDScreen: + md_bg_color: app.theme_cls.backgroundColor -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-persistent.gif - :align: center + MDTextField: + mode: "outlined" + size_hint_x: None + width: "240dp" + pos_hint: {"center_x": .5, "center_y": .5} -Helper text mode `'on_error'` ------------------------------ + MDTextFieldLeadingIcon: + icon: "account" -To display an error in a text field when using the -``helper_text_mode: "on_error"`` parameter, set the `"error"` text field -parameter to `True`: + MDTextFieldHintText: + text: "Outlined" -.. code-block:: python + MDTextFieldHelperText: + text: "Helper text" + mode: "persistent" - from kivy.lang import Builder + MDTextFieldTrailingIcon: + icon: "information" - from kivymd.app import MDApp - - KV = ''' - MDScreen: - - MDTextField: - id: text_field_error - hint_text: "Helper text on error (press 'Enter')" - helper_text: "There will always be a mistake" - helper_text_mode: "on_error" - pos_hint: {"center_x": .5, "center_y": .5} - size_hint_x: .5 - ''' + MDTextFieldMaxLengthText: + max_text_length: 10 + ''' - class Test(MDApp): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.screen = Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - self.screen.ids.text_field_error.bind( - on_text_validate=self.set_error_message, - on_focus=self.set_error_message, + + Example().run() + + .. tab:: Declarative Python style + + .. code-block:: python + + from kivymd.uix.textfield import ( + MDTextField, + MDTextFieldLeadingIcon, + MDTextFieldHintText, + MDTextFieldHelperText, + MDTextFieldTrailingIcon, + MDTextFieldMaxLengthText, ) - return self.screen - def set_error_message(self, instance_textfield): - self.screen.ids.text_field_error.error = True + from kivymd.uix.screen import MDScreen + from kivymd.app import MDApp - Test().run() + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return MDScreen( + MDTextField( + MDTextFieldLeadingIcon( + icon="account", + ), + MDTextFieldHintText( + text="Hint text", + ), + MDTextFieldHelperText( + text="Helper text", + mode="persistent", + ), + MDTextFieldTrailingIcon( + icon="information", + ), + MDTextFieldMaxLengthText( + max_text_length=10, + ), + mode="outlined", + size_hint_x=None, + width="240dp", + pos_hint={"center_x": 0.5, "center_y": 0.5}, + ), + md_bg_color=self.theme_cls.backgroundColor, + ) -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-mode-on-error.gif + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-example.png :align: center -Helper text mode `'on_error'` (with required) ---------------------------------------------- +API break +========= + +1.2.0 version +------------- .. code-block:: kv MDTextField: - hint_text: "required = True" - text: "required = True" - required: True - helper_text_mode: "on_error" - helper_text: "Enter text" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-required.gif - :align: center - -Text length control -------------------- - -.. code-block:: kv - - MDTextField: - hint_text: "Max text length = 5" - max_text_length: 5 - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-length.gif - :align: center - - -Multi line text ---------------- - -.. code-block:: kv - - MDTextField: - multiline: True - hint_text: "Multi-line text" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-multi-line.gif - :align: center - -Rectangle mode --------------- - -.. code-block:: kv - - MDTextField: - hint_text: "Rectangle mode" mode: "rectangle" + hint_text: "Hint text" + helper_text: "Helper text" + helper_text_mode: "persistent" + max_text_length: 10 + icon_right: "information" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rectangle-mode.gif - :align: center +2.0.0 version +------------- -Fill mode ---------- +.. note:: The text field with the `round` type was removed in version `2.0.0`. .. code-block:: kv MDTextField: - hint_text: "Fill mode" - mode: "fill" + mode: "outlined" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode.gif - :align: center + MDTextFieldLeadingIcon: + icon: "phone" -Round mode ---------- + MDTextFieldTrailingIcon: + icon: "information" -.. code-block:: kv + MDTextFieldHintText: + text: "Hint text" - MDTextField: - hint_text: "Round mode" - mode: "round" - max_text_length: 15 - helper_text: "Massage" + MDTextFieldHelperText: + text: "Helper text" + mode: "persistent" -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-round-mode.gif - :align: center - -.. MDTextFieldRect: -MDTextFieldRect ---------------- - -.. Note:: :class:`~MDTextFieldRect` inherited from - :class:`~kivy.uix.textinput.TextInput`. You can use all parameters and - attributes of the :class:`~kivy.uix.textinput.TextInput` class in the - :class:`~MDTextFieldRect` class. - -.. code-block:: kv - - MDTextFieldRect: - size_hint: 1, None - height: "30dp" - background_color: app.theme_cls.bg_normal - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-rect.gif - :align: center - -.. Warning:: While there is no way to change the color of the border. - -Clickable icon for MDTextField ------------------------------- - -.. code-block:: python - - from kivy.lang import Builder - from kivy.properties import StringProperty - - from kivymd.app import MDApp - from kivymd.uix.relativelayout import MDRelativeLayout - - KV = ''' - : - size_hint_y: None - height: text_field.height - - MDTextField: - id: text_field - hint_text: root.hint_text - text: root.text - password: True - icon_left: "key-variant" - - MDIconButton: - icon: "eye-off" - pos_hint: {"center_y": .5} - pos: text_field.width - self.width + dp(8), 0 - theme_text_color: "Hint" - on_release: - self.icon = "eye" if self.icon == "eye-off" else "eye-off" - text_field.password = False if text_field.password is True else True - - - MDScreen: - - ClickableTextFieldRound: - size_hint_x: None - width: "300dp" - hint_text: "Password" - pos_hint: {"center_x": .5, "center_y": .5} - ''' - - - class ClickableTextFieldRound(MDRelativeLayout): - text = StringProperty() - hint_text = StringProperty() - # Here specify the required parameters for MDTextFieldRound: - # [...] - - - class Test(MDApp): - def build(self): - return Builder.load_string(KV) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-clickable_right-icon.gif - :align: center - -.. seealso:: - - See more information in the :class:`~MDTextFieldRect` class. + MDTextFieldMaxLengthText: + max_text_length: 10 """ -__all__ = ("MDTextField", "MDTextFieldRect") +from __future__ import annotations + +__all__ = ( + "BaseTextFieldIcon", + "BaseTextFieldLabel", + "Validator", + "AutoFormatTelephoneNumber", + "MDTextField", + "MDTextFieldHelperText", + "MDTextFieldMaxLengthText", + "MDTextFieldHintText", + "MDTextFieldLeadingIcon", + "MDTextFieldTrailingIcon", +) import os import re from datetime import date -from typing import Union from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder -from kivy.metrics import dp, sp +from kivy.metrics import dp from kivy.properties import ( - AliasProperty, BooleanProperty, ColorProperty, ListProperty, @@ -297,15 +253,16 @@ from kivy.properties import ( ObjectProperty, OptionProperty, StringProperty, + VariableListProperty, ) -from kivy.uix.label import Label from kivy.uix.textinput import TextInput from kivymd import uix_path from kivymd.font_definitions import theme_font_styles -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import DeclarativeBehavior -from kivymd.uix.label import MDIcon +from kivymd.theming import ThemableBehavior, ThemeManager +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior +from kivymd.uix.label import MDIcon, MDLabel with open( os.path.join(uix_path, "textfield", "textfield.kv"), encoding="utf-8" @@ -320,19 +277,24 @@ class AutoFormatTelephoneNumber: """ Implements automatic formatting of the text entered in the text field according to the mask, for example '+38 (###) ### ## ##'. + + .. warning:: This class has not yet been implemented and it is not + recommended to use it yet. """ def __init__(self): self._backspace = False - def isnumeric(self, value): + def isnumeric(self, value) -> bool: try: int(value) return True except ValueError: return False - def do_backspace(self, *args): + def do_backspace(self, *args) -> None: + """Do backspace operation from the current cursor position.""" + if self.validator and self.validator == "phone": self._backspace = True text = self.text @@ -340,7 +302,7 @@ class AutoFormatTelephoneNumber: self.text = text self._backspace = False - def field_filter(self, value, boolean): + def field_filter(self, value, boolean) -> None: if self.validator and self.validator == "phone": if len(self.text) == 14: return @@ -348,7 +310,7 @@ class AutoFormatTelephoneNumber: return value return value - def format(self, value): + def format(self, value) -> None: if value != "" and not value.isspace() and not self._backspace: if len(value) <= 1 and self.focus: self.text = value @@ -418,11 +380,15 @@ class Validator: """ def is_email_valid(self, text: str) -> bool: + """Checks the validity of the email.""" + if not re.match(r"[^@]+@[^@]+\.[^@]+", text): return True return False def is_time_valid(self, text: str) -> bool: + """Checks the validity of the time.""" + if re.match(r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$", text) or re.match( r"^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$", text ): @@ -431,6 +397,8 @@ class Validator: return True def is_date_valid(self, text: str) -> bool: + """Checks the validity of the date.""" + if not self.date_format: raise Exception("TextInput date_format was not defined.") @@ -439,24 +407,24 @@ class Validator: mm = "[0][1-9]|[1][0-2]" yyyy = "[0-9][0-9][0-9][0-9]" fmt = self.date_format.split("/") - largs = locals() + args = locals() # Access the local variables dict in the correct format based on # date_format split. Example: "mm/dd/yyyy" -> ["mm", "dd", "yyyy"] - # largs[fmt[0]] would be largs["mm"] so the month regex string. + # args[fmt[0]] would be args["mm"] so the month regex string. if re.match( - f"^({largs[fmt[0]]})/({largs[fmt[1]]})/({largs[fmt[2]]})$", text + f"^({args[fmt[0]]})/({args[fmt[1]]})/({args[fmt[2]]})$", text ): input_split = text.split("/") - largs[fmt[0]] = input_split[0] - largs[fmt[1]] = input_split[1] - largs[fmt[2]] = input_split[2] + args[fmt[0]] = input_split[0] + args[fmt[1]] = input_split[1] + args[fmt[2]] = input_split[2] # Organize input into correct slots and try to convert # to datetime object. This way February exceptions are # tested. Also tests with the date_interval are simpler # using datetime objects. try: datetime = date( - int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"]) + int(args["yyyy"]), int(args["mm"]), int(args["dd"]) ) except ValueError: return True @@ -482,7 +450,7 @@ class Validator: raise Exception("TextInput date_format was not defined.") fmt = self.date_format.split("/") - largs = {} + args = {} # Convert string inputs into datetime.date objects and store # them back into self.date_interval. try: @@ -490,28 +458,28 @@ class Validator: self.date_interval[0], date ): split = self.date_interval[0].split("/") - largs[fmt[0]] = split[0] - largs[fmt[1]] = split[1] - largs[fmt[2]] = split[2] + args[fmt[0]] = split[0] + args[fmt[1]] = split[1] + args[fmt[2]] = split[2] self.date_interval[0] = date( - int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"]) + int(args["yyyy"]), int(args["mm"]), int(args["dd"]) ) if self.date_interval[1] and not isinstance( self.date_interval[1], date ): split = self.date_interval[1].split("/") - largs[fmt[0]] = split[0] - largs[fmt[1]] = split[1] - largs[fmt[2]] = split[2] + args[fmt[0]] = split[0] + args[fmt[1]] = split[1] + args[fmt[2]] = split[2] self.date_interval[1] = date( - int(largs["yyyy"]), int(largs["mm"]), int(largs["dd"]) + int(args["yyyy"]), int(args["mm"]), int(args["dd"]) ) except Exception: raise Exception( - r"TextInput date_interval was defined incorrectly, it must " - r"be composed of objects or strings" - r" following current date_format." + r"TextInput date_interval was defined incorrectly, " + r"it must be composed of objects " + r"or strings following current date_format." ) # Test if the interval is valid. @@ -520,140 +488,458 @@ class Validator: ): if self.date_interval[0] >= self.date_interval[1]: raise Exception( - "TextInput date_interval last date must be greater" - " than the first date or set to None." + "TextInput date_interval last date must be greater " + "than the first date or set to None." ) Clock.schedule_once(lambda x: on_date_interval()) -class MDTextFieldRect(ThemableBehavior, TextInput): +class BaseTextFieldLabel(MDLabel): """ - Textfield rect class. + Base texture for :class:`~MDTextField` class (helper text, max length, + hint text). For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.textinput.TextInput` - classes documentation. + :class:`~kivymd.uix.label.label.MDLabel` class documentation. + + .. versionadded:: 2.0.0 """ - line_anim = BooleanProperty(True) + text_color_normal = ColorProperty(None) """ - If True, then text field shows animated line when on focus. + Text color in (r, g, b, a) or string format when text field is out + of focus. - :attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + The property was moved from class:`~MDTextField` class and renamed + from `helper_text_color_normal` to `text_color_normal`. + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldHintText: + text: "Hint text color normal" + text_color_normal: "brown" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-text-color-normal.png + :align: center + + :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - def get_rect_instruction(self): - canvas_instructions = self.canvas.after.get_group("rectangle") - return canvas_instructions[0] - - _rectangle = AliasProperty(get_rect_instruction, cache=True) + text_color_focus = ColorProperty(None) """ - It is the :class:`~kivy.graphics.vertex_instructions.Line` - instruction reference of the field rectangle. + Text color in (r, g, b, a) or string format when the text field has + focus. - :attr:`_rectangle` is an :class:`~kivy.properties.AliasProperty`. + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + The property was moved from class:`~MDTextField` class and renamed + from `helper_text_color_focus` to `text_color_focus`. + + .. code-block:: kv + + MDTextField: + + MDTextFieldHelperText: + text: "Helper text color focus" + text_color_focus: "brown" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-text-color-focus.png + :align: center + + :attr:`text_color_focus` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. """ - def get_color_instruction(self): - canvas_instructions = self.canvas.after.get_group("color") - return canvas_instructions[0] - _rectangle_color = AliasProperty(get_color_instruction, cache=True) +class MDTextFieldHelperText(BaseTextFieldLabel): """ - It is the :class:`~kivy.graphics.context_instructions.Color` - instruction reference of the field rectangle. + Implements the helper text label. - :attr:`_rectangle_color` is an :class:`~kivy.properties.AliasProperty`. + For more information, see in the :class:`~BaseTextFieldLabel` + class documentation. + + .. versionadded:: 2.0.0 """ - _primary_color = ColorProperty((0, 0, 0, 0)) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._update_primary_color() - self.theme_cls.bind(primary_color=self._update_primary_color) - - def anim_rect(self, points, alpha): - if alpha == 1: - d_line = 0.3 - d_color = 0.4 - else: - d_line = 0.05 - d_color = 0.05 - - Animation( - points=points, d=(d_line if self.line_anim else 0), t="out_cubic" - ).start(self._rectangle) - Animation(a=alpha, d=(d_color if self.line_anim else 0)).start( - self._rectangle_color - ) - - def _update_primary_color(self, *args): - self._primary_color = self.theme_cls.primary_color - self._primary_color[3] = 0 - - -class TextfieldLabel(ThemableBehavior, Label): - """Base texture for :class:`~MDTextField` class.""" - - font_style = OptionProperty("Body1", options=theme_font_styles) - # - field = ObjectProperty() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.font_size = sp(self.theme_cls.font_styles[self.font_style][1]) - - -class MDTextField( - DeclarativeBehavior, - ThemableBehavior, - TextInput, - Validator, - AutoFormatTelephoneNumber, -): - """ - Textfield class. - - For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.textinput.TextInput` and - :class:`~Validator` and - :class:`~AutoFormatTelephoneNumber` - classes documentation. - """ - - helper_text = StringProperty() - """ - Text for ``helper_text`` mode. - - :attr:`helper_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - helper_text_mode = OptionProperty( + mode = OptionProperty( "on_focus", options=["on_error", "persistent", "on_focus"] ) """ Helper text mode. Available options are: `'on_error'`, `'persistent'`, `'on_focus'`. - :attr:`helper_text_mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'none'`. + .. versionchanged:: 2.0.0 + The property was moved from class:`~MDTextField` class and renamed + from `helper_text_mode` to `mode`. + + On focus + -------- + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldHelperText: + text: "Helper text" + mode: "on_focus" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-helper-text-mode-on-focus.gif + :align: center + + On error + -------- + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldHelperText: + text: "Helper text" + mode: "on_error" + + MDTextFieldMaxLengthText: + max_text_length: 5 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-helper-text-mode-on-error.gif + :align: center + + Persistent + ---------- + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldHelperText: + text: "Helper text" + mode: "persistent" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-helper-text-mode-persistent.gif + :align: center + + :attr:`mode` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'on_focus'`. + """ + + +class MDTextFieldMaxLengthText(BaseTextFieldLabel): + """ + Implements the max length text label. + + For more information, see in the :class:`~BaseTextFieldLabel` + class documentation. + + .. versionadded:: 2.0.0 """ max_text_length = NumericProperty(None) """ Maximum allowed value of characters in a text field. + .. versionchanged:: 2.0.0 + The property was moved from class:`~MDTextField`. + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldMaxLengthText: + max_text_length: 10 + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-max-text-length.png + :align: center + :attr:`max_text_length` is an :class:`~kivy.properties.NumericProperty` and defaults to `None`. """ + +class MDTextFieldHintText(BaseTextFieldLabel): + """ + Implements the hint text label. + + For more information, see in the :class:`~BaseTextFieldLabel` + class documentation. + + .. versionadded:: 2.0.0 + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldHintText: + text: "Hint text" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-hint-text.gif + :align: center + """ + + +class BaseTextFieldIcon(MDIcon): + """ + Base texture for :class:`~MDTextField` class (helper text, max length, + hint text). + + For more information, see in the :class:`~kivymd.uix.label.label.MDIcon` + class documentation. + + .. versionchanged:: 2.0.0 + """ + + icon_color_normal = ColorProperty(None) + """ + Icon color in (r, g, b, a) or string format when text field is out + of focus. + + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + The property was moved from class:`~MDTextField` class and renamed + from `icon_right_color_normal/icon_left_color_normal` + to `icon_color_normal`. + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldLeadingIcon: + icon: "phone" + theme_icon_color: "Custom" + icon_color_normal: "lightgreen" + + MDTextFieldHintText: + text: "Leading icon color normal" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-leading-icon-color-normal.png + :align: center + + :attr:`icon_color_normal` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + icon_color_focus = ColorProperty(None) + """ + Icon color in (r, g, b, a) or string format when the text field has + focus. + + .. versionadded:: 1.0.0 + + .. versionchanged:: 2.0.0 + The property was moved from class:`~MDTextField` class and renamed + from `icon_right_color_focus/icon_left_color_focus ` + to `icon_color_focus`. + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldLeadingIcon: + icon: "phone" + theme_icon_color: "Custom" + icon_color_focus: "lightgreen" + + MDTextFieldHintText: + text: "Leading icon color focus" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-leading-icon-color-focus.png + :align: center + + :attr:`icon_color_focus` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + +class MDTextFieldLeadingIcon(BaseTextFieldIcon): + """ + Implements the leading icon. + + For more information, see in the :class:`~BaseTextFieldIcon` + class documentation. + + .. versionadded:: 2.0.0 + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldLeadingIcon: + icon: "phone" + + MDTextFieldHintText: + text: "Field with leading icon" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-leading-icon.png + :align: center + """ + + +class MDTextFieldTrailingIcon(BaseTextFieldIcon): + """ + Implements the trailing icon. + + For more information, see in the :class:`~BaseTextFieldIcon` + class documentation. + + .. versionadded:: 2.0.0 + + .. code-block:: kv + + MDTextField: + mode: "filled" + + MDTextFieldTrailingIcon: + icon: "phone" + + MDTextFieldHintText: + text: "Field with trailing icon" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-trailing-icon.png + :align: center + """ + + +# TODO: Add a custom color for the disabled state of the text field. +class MDTextField( + DeclarativeBehavior, + StateLayerBehavior, + ThemableBehavior, + TextInput, + Validator, + AutoFormatTelephoneNumber, + BackgroundColorBehavior, +): + """ + Textfield class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivy.uix.textinput.TextInput` and + :class:`~Validator` and + :class:`~AutoFormatTelephoneNumber` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` + classes documentation. + """ + + font_style = StringProperty("Body") + """ + Name of the style for the input text. + + .. versionadded:: 2.0.0 + + .. seealso:: + + `Font style names `_ + + :attr:`font_style` is an :class:`~kivy.properties.StringProperty` + and defaults to `'Body'`. + """ + + role = StringProperty("large") + """ + Role of font style. + + .. versionadded:: 2.0.0 + + .. seealso:: + + `Font style roles `_ + + :attr:`role` is an :class:`~kivy.properties.StringProperty` + and defaults to `'large'`. + """ + + mode = OptionProperty("outlined", options=["outlined", "filled"]) + """ + Text field mode. Available options are: `'outlined'`, `'filled'`. + + :attr:`mode` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'outlined'`. + """ + + error_color = ColorProperty(None) + """ + Error color in (r, g, b, a) or string format for `required = True` + or when the text field is in `error` state. + + :attr:`error_color` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + error = BooleanProperty(False) + """ + If True, then the text field goes into `error` mode. + + :attr:`error` is an :class:`~kivy.properties.BooleanProperty` + and defaults to `False`. + """ + + text_color_normal = ColorProperty(None) + """ + Text color in (r, g, b, a) or string format when text field is out of focus. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDTextField: + theme_text_color: "Custom" + text_color_normal: "green" + text: "Text color normal" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.png + :align: center + + :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + text_color_focus = ColorProperty(None) + """ + Text color in (r, g, b, a) or string format when text field has focus. + + .. versionadded:: 1.0.0 + + .. code-block:: kv + + MDTextField: + theme_text_color: "Custom" + text_color_focus: "green" + text: "Text color focus" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.png + :align: center + + :attr:`text_color_focus` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + radius = VariableListProperty([dp(4), dp(4), dp(4), dp(4)]) + """ + The corner radius for a text field in `filled/outlined` mode. + + :attr:`radius` is a :class:`~kivy.properties.VariableListProperty` and + defaults to `[dp(4), dp(4), 0, 0]`. + """ + required = BooleanProperty(False) """ Required text. If True then the text field requires text. @@ -662,18 +948,128 @@ class MDTextField( and defaults to `False`. """ - mode = OptionProperty( - "line", options=["rectangle", "round", "fill", "line"] - ) + line_color_normal = ColorProperty(None) """ - Text field mode. - Available options are: `'line'`, `'rectangle'`, `'fill'`, `'round'`. + Line color normal (active indicator) in (r, g, b, a) or string format. - :attr:`mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'line'`. + .. code-block:: kv + + MDTextField: + mode: "filled" + theme_line_color: "Custom" + line_color_normal: "green" + + MDTextFieldHelperText: + text: "Line color normal" + mode: "persistent" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-line-color-normal.png + :align: center + + :attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + line_color_focus = ColorProperty(None) + """ + Line color focus (active indicator) in (r, g, b, a) or string format. + + .. code-block:: kv + + MDTextField: + mode: "filled" + theme_line_color: "Custom" + line_color_focus: "green" + + MDTextFieldHelperText: + text: "Line color focus" + mode: "persistent" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-line-color-focus.png + :align: center + + :attr:`line_color_focus` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + fill_color_normal = ColorProperty(None) + """ + Fill background color in (r, g, b, a) or string format in 'fill' mode when] + text field is out of focus. + + .. code-block:: kv + + MDTextField: + mode: "filled" + theme_bg_color: "Custom" + fill_color_normal: 0, 1, 0, .2 + + MDTextFieldHelperText: + text: "Fill color normal" + mode: "persistent" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-fill-color-normal.png + :align: center + + :attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + fill_color_focus = ColorProperty(None) + """ + Fill background color in (r, g, b, a) or string format in 'fill' mode when + the text field has focus. + + .. code-block:: kv + + MDTextField: + mode: "filled" + theme_bg_color: "Custom" + fill_color_focus: 0, 1, 0, .2 + + MDTextFieldHelperText: + text: "Fill color focus" + mode: "persistent" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-fill-color-focus.png + :align: center + + :attr:`fill_color_focus` is an :class:`~kivy.properties.ColorProperty` + and defaults to `None`. + """ + + # TODO: Add minimum allowed height. Otherwise, if the value is, + # for example, 20, the text field will simply be lessened. + max_height = NumericProperty(0) + """ + Maximum height of the text box when `multiline = True`. + + .. code-block:: kv + + MDTextField: + mode: "filled" + max_height: "200dp" + multiline: True + + MDTextFieldHelperText: + text: "multiline=True" + mode: "persistent" + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-multiline.gif + :align: center + + :attr:`max_height` is a :class:`~kivy.properties.NumericProperty` and + defaults to `0`. """ phone_mask = StringProperty("") + """ + This property has not yet been implemented and it is not recommended to + use it yet. + + :attr:`phone_mask` is a :class:`~kivy.properties.StringProperty` and + defaults to ''. + """ validator = OptionProperty(None, options=["date", "email", "time", "phone"]) """ @@ -686,1195 +1082,721 @@ class MDTextField( .. versionadded:: 1.1.0 - .. code-block:: python + .. code-block:: kv MDTextField: - hint_text: "Email" - helper_text: "user@gmail.com" + mode: "filled" validator: "email" + + MDTextFieldHintText: + text: "Email" + + MDTextFieldHelperText: + text: "user@gmail.com" + mode: "persistent" - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-email-validator.png :align: center - .. tabs:: + .. code-block:: python - .. tab:: Declarative KV style + from kivy.lang import Builder - .. code-block:: python + from kivymd.app import MDApp - from kivy.lang import Builder + KV = ''' + MDScreen: + md_bg_color: self.theme_cls.backgroundColor - from kivymd.app import MDApp + MDBoxLayout: + orientation: "vertical" + spacing: "20dp" + adaptive_height: True + size_hint_x: .8 + pos_hint: {"center_x": .5, "center_y": .5} - KV = ''' - MDScreen: + MDTextField: + validator: "date" + date_format: "dd/mm/yyyy" - MDBoxLayout: - orientation: "vertical" - spacing: "20dp" - adaptive_height: True - size_hint_x: .8 - pos_hint: {"center_x": .5, "center_y": .5} + MDTextFieldHintText: + text: "Date dd/mm/yyyy without limits" - MDTextField: - hint_text: "Date dd/mm/yyyy without limits" - helper_text: "Enter a valid dd/mm/yyyy date" - validator: "date" - date_format: "dd/mm/yyyy" + MDTextFieldHelperText: + text: "Enter a valid dd/mm/yyyy date" - MDTextField: - hint_text: "Date mm/dd/yyyy without limits" - helper_text: "Enter a valid mm/dd/yyyy date" - validator: "date" - date_format: "mm/dd/yyyy" + MDTextField: + validator: "date" + date_format: "mm/dd/yyyy" - MDTextField: - hint_text: "Date yyyy/mm/dd without limits" - helper_text: "Enter a valid yyyy/mm/dd date" - validator: "date" - date_format: "yyyy/mm/dd" + MDTextFieldHintText: + text: "Date mm/dd/yyyy without limits" - MDTextField: - hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval" - helper_text: "Enter a valid dd/mm/yyyy date" - validator: "date" - date_format: "dd/mm/yyyy" - date_interval: "01/01/1900", "01/01/2100" + MDTextFieldHelperText: + text: "Enter a valid mm/dd/yyyy date" - MDTextField: - hint_text: "Date dd/mm/yyyy in [01/01/1900, None] interval" - helper_text: "Enter a valid dd/mm/yyyy date" - validator: "date" - date_format: "dd/mm/yyyy" - date_interval: "01/01/1900", None + MDTextField: + validator: "date" + date_format: "yyyy/mm/dd" - MDTextField: - hint_text: "Date dd/mm/yyyy in [None, 01/01/2100] interval" - helper_text: "Enter a valid dd/mm/yyyy date" - validator: "date" - date_format: "dd/mm/yyyy" - date_interval: None, "01/01/2100" - ''' + MDTextFieldHintText: + text: "Date yyyy/mm/dd without limits" + + MDTextFieldHelperText: + text: "Enter a valid yyyy/mm/dd date" + + MDTextField: + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: "01/01/1900", "01/01/2100" + + MDTextFieldHintText: + text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval" + + MDTextFieldHelperText: + text: "Enter a valid dd/mm/yyyy date" + + MDTextField: + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: "01/01/1900", None + + MDTextFieldHintText: + text: "Date dd/mm/yyyy in [01/01/1900, None] interval" + + MDTextFieldHelperText: + text: "Enter a valid dd/mm/yyyy date" + + MDTextField: + validator: "date" + date_format: "dd/mm/yyyy" + date_interval: None, "01/01/2100" + + MDTextFieldHintText: + text: "Date dd/mm/yyyy in [None, 01/01/2100] interval" + + MDTextFieldHelperText: + text: "Enter a valid dd/mm/yyyy date" + ''' - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) - Test().run() + Example().run() - .. tab:: Declarative python style - - .. code-block:: python - - from kivymd.app import MDApp - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.screen import MDScreen - from kivymd.uix.textfield import MDTextField - - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return ( - MDScreen( - MDBoxLayout( - MDTextField( - hint_text="Date dd/mm/yyyy without limits", - helper_text="Enter a valid dd/mm/yyyy date", - validator="date", - date_format="dd/mm/yyyy", - ), - MDTextField( - hint_text="Date mm/dd/yyyy without limits", - helper_text="Enter a valid mm/dd/yyyy date", - validator="date", - date_format="mm/dd/yyyy", - ), - MDTextField( - hint_text="Date yyyy/mm/dd without limits", - helper_text="Enter a valid yyyy/mm/dd date", - validator="date", - date_format="yyyy/mm/dd", - ), - MDTextField( - hint_text="Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval", - helper_text="Enter a valid dd/mm/yyyy date", - validator="date", - date_format="dd/mm/yyyy", - date_interval=["01/01/1900", "01/01/2100"], - ), - MDTextField( - hint_text="Date dd/mm/yyyy in [01/01/1900, None] interval", - helper_text="Enter a valid dd/mm/yyyy date", - validator="date", - date_format="dd/mm/yyyy", - date_interval=["01/01/1900", None], - ), - MDTextField( - hint_text="Date dd/mm/yyyy in [None, 01/01/2100] interval", - helper_text="Enter a valid dd/mm/yyyy date", - validator="date", - date_format="dd/mm/yyyy", - date_interval=[None, "01/01/2100"], - ), - orientation="vertical", - spacing="20dp", - adaptive_height=True, - size_hint_x=0.8, - pos_hint={"center_x": 0.5, "center_y": 0.5}, - ) - ) - ) - - - Test().run() - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-validator-date.png + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-fields-validator-date.png :align: center :attr:`validator` is an :class:`~kivy.properties.OptionProperty` and defaults to `None`. """ - line_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Line color normal (static underline line) in (r, g, b, a) or string format. - - .. code-block:: kv - - MDTextField: - hint_text: "line_color_normal" - line_color_normal: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-normal.png - :align: center - - :attr:`line_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - line_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Line color focus (active underline line) in (r, g, b, a) or string format. - - .. code-block:: kv - - MDTextField: - hint_text: "line_color_focus" - line_color_focus: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-line-color-focus.gif - :align: center - - :attr:`line_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - line_anim = BooleanProperty(True) - """ - If True, then text field shows animated underline when on focus. - - :attr:`line_anim` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - error_color = ColorProperty([0, 0, 0, 0]) - """ - Error color in (r, g, b, a) or string format for ``required = True``. - - :attr:`error_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - fill_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Fill background color in (r, g, b, a) or string format in 'fill' mode when] - text field is out of focus. - - .. code=block:: kv - - MDTextField: - hint_text: "Fill mode" - mode: "fill" - fill_color_normal: "brown" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-normal.png - :align: center - - :attr:`fill_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - fill_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Fill background color in (r, g, b, a) or string format in 'fill' mode when - the text field has focus. - - .. code=block:: kv - - MDTextField: - hint_text: "Fill mode" - mode: "fill" - fill_color_focus: "brown" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-color-focus.gif - :align: center - - :attr:`fill_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - active_line = BooleanProperty(True) - """ - Show active line or not. - - :attr:`active_line` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - error = BooleanProperty(False) - """ - If True, then the text field goes into ``error`` mode. - - :attr:`error` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - hint_text_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Hint text color in (r, g, b, a) or string format when text field is out - of focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - hint_text: "hint_text_color_normal" - hint_text_color_normal: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-normal.png - :align: center - - :attr:`hint_text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - hint_text_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Hint text color in (r, g, b, a) or string format when the text field has - focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - hint_text: "hint_text_color_focus" - hint_text_color_focus: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-hint-text-color-focus.gif - :align: center - - :attr:`hint_text_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - helper_text_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Helper text color in (r, g, b, a) or string format when text field is out - of focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - helper_text: "helper_text_color_normal" - helper_text_mode: "persistent" - helper_text_color_normal: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-normal.png - :align: center - - :attr:`helper_text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - helper_text_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Helper text color in (r, g, b, a) or string format when the text field has - focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - helper_text: "helper_text_color_focus" - helper_text_mode: "persistent" - helper_text_color_focus: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-helper-text-color-focus.gif - :align: center - - :attr:`helper_text_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - icon_right_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Color in (r, g, b, a) or string format of right icon when text field is out - of focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - icon_right: "language-python" - hint_text: "icon_right_color_normal" - icon_right_color_normal: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-normal.png - :align: center - - :attr:`icon_right_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - icon_right_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Color in (r, g, b, a) or string format of right icon when the text field - has focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - icon_right: "language-python" - hint_text: "icon_right_color_focus" - icon_right_color_focus: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-icon-right-color-focus.gif - :align: center - - :attr:`icon_right_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - icon_left_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Color in (r, g, b, a) or string format of right icon when text field is out - of focus. - - .. versionadded:: 1.0.0 - - :attr:`icon_left_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - icon_left_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Color in (r, g, b, a) or string format of right icon when the text field - has focus. - - .. versionadded:: 1.0.0 - - :attr:`icon_left_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - max_length_text_color = ColorProperty([0, 0, 0, 0]) - """ - Text color in (r, g, b, a) or string format of the maximum length of - characters to be input. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - hint_text: "max_length_text_color" - max_length_text_color: "red" - max_text_length: 5 - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-max-length-text-color.png - :align: center - - :attr:`max_length_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - icon_right = StringProperty() - """ - Right icon texture. - - .. note:: It's just a texture. It has no press/touch events. - - :attr:`icon_right` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - icon_left = StringProperty() - """ - Left icon texture. - - .. versionadded:: 1.0.0 - - .. note:: It's just a texture. It has no press/touch events. - Also note that you cannot use the left and right icons at the same time yet. - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-left-icon.png - :align: center - - :attr:`icon_left` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - text_color_normal = ColorProperty([0, 0, 0, 0]) - """ - Text color in (r, g, b, a) or string format when text field is out of focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - hint_text: "text_color_normal" - text_color_normal: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-normal.png - :align: center - - :attr:`text_color_normal` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - text_color_focus = ColorProperty([0, 0, 0, 0]) - """ - Text color in (r, g, b, a) or string format when text field has focus. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTextField: - hint_text: "text_color_focus" - text_color_focus: "red" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-text-color-focus.gif - :align: center - - :attr:`text_color_focus` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - font_size = NumericProperty("16sp") - """ - Font size of the text in pixels. - - :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` and - defaults to `'16sp'`. - """ - - # TODO: Add minimum allowed height. Otherwise, if the value is, - # for example, 20, the text field will simply be lessened. - max_height = NumericProperty(0) - """ - Maximum height of the text box when `multiline = True`. - - .. code-block:: kv - - MDTextField: - size_hint_x: .5 - hint_text: "multiline=True" - max_height: "200dp" - mode: "fill" - fill_color: 0, 0, 0, .4 - multiline: True - pos_hint: {"center_x": .5, "center_y": .5} - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/text-field-fill-mode-multiline-max-height.gif - :align: center - - :attr:`max_height` is a :class:`~kivy.properties.NumericProperty` and - defaults to `0`. - """ - - radius = ListProperty([10, 10, 0, 0]) - """ - The corner radius for a text field in `fill/rectangle` mode. - - :attr:`radius` is a :class:`~kivy.properties.ListProperty` and - defaults to `[10, 10, 0, 0]`. - """ - - font_name_helper_text = StringProperty("Roboto") - """ - Font name for helper text. - - :attr:`font_name_helper_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - font_name_hint_text = StringProperty("Roboto") - """ - Font name for hint text. - - :attr:`font_name_hint_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - - font_name_max_length = StringProperty("Roboto") - """ - Font name for max text length. - - :attr:`font_name_max_length` is an :class:`~kivy.properties.StringProperty` - and defaults to `'Roboto'`. - """ - + # Helper text label object. + _helper_text_label = ObjectProperty() + # Hint text label object. + _hint_text_label = ObjectProperty() + # Leading icon object. + _leading_icon = ObjectProperty() + # Trailing icon object. + _trailing_icon = ObjectProperty() + # Max length label object. + _max_length_label = ObjectProperty() + # Maximum length of characters to be input. + _max_length = "0" + # Active indicator height. + _indicator_height = NumericProperty(dp(1)) + # Outline height. + _outline_height = NumericProperty(dp(1)) # The x-axis position of the hint text in the text field. _hint_x = NumericProperty(0) # The y-axis position of the hint text in the text field. - _hint_y = NumericProperty("38dp") - # Width of underline that animates when the focus of the text field. - _underline_width = NumericProperty(0) - # Font size for hint text. - _hint_text_font_size = NumericProperty(sp(16)) - - # Label object for `helper_text` parameter. - _helper_text_label = None - # Label object for `max_text_length` parameter. - _max_length_label = None - # Label object for `hint_text` parameter. - _hint_text_label = None - # `MDIcon` object for the icon on the right. - _icon_right_label = None - # `MDIcon` object for the icon on the left. - _icon_left_label = None - - # The left and right coordinates of the text field in 'rectangle' mode. - # - # ┍──blank_space_left blank_space_right──────────────┑ - # | | - # | | - # | | - # ┕──────────────────────────────────────────────────────┙ - _line_blank_space_right_point = NumericProperty(0) - _line_blank_space_left_point = NumericProperty(0) - - # The values of colors that are used in the KV file to display the color - # of the corresponding texture. - _fill_color = ColorProperty([0, 0, 0, 0]) - _text_color_normal = ColorProperty([0, 0, 0, 0]) - _hint_text_color = ColorProperty([0, 0, 0, 0]) - _helper_text_color = ColorProperty([0, 0, 0, 0]) - _max_length_text_color = ColorProperty([0, 0, 0, 0]) - _icon_right_color = ColorProperty([0, 0, 0, 0]) - _icon_left_color = ColorProperty([0, 0, 0, 0]) - _line_color_normal = ColorProperty([0, 0, 0, 0]) - _line_color_focus = ColorProperty([0, 0, 0, 0]) - - # Text to restore the text of the tale after clearing the text field. - __hint_text = StringProperty() - # List of color attribute names that should be updated when changing the - # application color palette. - _colors_to_updated = ListProperty() + _hint_y = NumericProperty(0) + # The right/left lines coordinates of the text field in 'outlined' mode. + _left_x_axis_pos = NumericProperty(dp(32)) + _right_x_axis_pos = NumericProperty(dp(32)) def __init__(self, *args, **kwargs): - self.set_objects_labels() - Clock.schedule_once(self._set_attr_names_to_updated) - Clock.schedule_once(self.set_colors_to_updated) - Clock.schedule_once(self.set_default_colors) super().__init__(*args, **kwargs) - self.bind( - _hint_text_font_size=self._hint_text_label.setter("font_size"), - _icon_right_color=self._icon_right_label.setter("text_color"), - _icon_left_color=self._icon_left_label.setter("text_color"), - font_name_hint_text=self._hint_text_label.setter("font_name"), - text=self.set_text, - ) + self.bind(text=self.set_text) self.theme_cls.bind( - primary_color=self.set_default_colors, - theme_style=self.set_default_colors, + primary_palette=self.update_colors, + theme_style=self.update_colors, ) - Clock.schedule_once(self.check_text) + Clock.schedule_once(self._check_text) - # TODO: Is this method necessary? - # During testing, a quick double-click on the text box does not stop - # the animation of the hint text height. - def cancel_all_animations_on_double_click(self) -> None: - """ - Cancels the animations of the text field when double-clicking on the - text field. - """ + def update_colors( + self, theme_manager: ThemeManager, theme_color: str + ) -> None: + """Fired when the `primary_palette` or `theme_style` value changes.""" - if ( - self._hint_y == dp(38) - and not self.text - or self._hint_y == dp(14) - and self.text - ): - Animation.cancel_all( - self, - "_underline_width", - "_hint_y", - "_hint_x", - "_hint_text_font_size", - ) + def update_colors(*args): + if not self.disabled: + self.on_focus(self, self.focus) + else: + self.on_disabled(self, self.disabled) - def set_colors_to_updated(self, interval: Union[float, int]) -> None: - for attr_name in self._attr_names_to_updated.keys(): - if getattr(self, attr_name) == [0, 0, 0, 0]: - self._colors_to_updated.append(attr_name) + Clock.schedule_once(update_colors, 1) - def set_default_colors( - self, interval: Union[float, int], updated: bool = False + def add_widget(self, widget, index=0, canvas=None): + if isinstance(widget, MDTextFieldHelperText): + self._helper_text_label = widget + if isinstance(widget, MDTextFieldHintText): + self._hint_text_label = widget + if isinstance(widget, MDTextFieldLeadingIcon): + self._leading_icon = widget + if isinstance(widget, MDTextFieldTrailingIcon): + self._trailing_icon = widget + if isinstance(widget, MDTextFieldMaxLengthText): + self._max_length_label = widget + else: + return super().add_widget(widget) + + def set_texture_color( + self, texture, canvas_group, color: list, error: bool = False ) -> None: """ - Sets the default text field colors when initializing a text field - object. Also called when the application palette changes. - - :param updated: If `True` - the color theme of the application has - been changed. Updating the meanings of the colors. + Animates the color of the + leading/trailing icons/hint/helper/max length text. """ - self._set_attr_names_to_updated(0) - for attr_name in self._attr_names_to_updated.keys(): - self._set_color( - attr_name, self._attr_names_to_updated[attr_name], updated - ) + def update_hint_text_rectangle(*args): + hint_text_rectangle = self.canvas.after.get_group( + "hint-text-rectangle" + )[0] + hint_text_rectangle.texture = None + texture.texture_update() + hint_text_rectangle.texture = texture.texture - if self.error_color == [0, 0, 0, 0] or updated: - self.error_color = ( - self.theme_cls.error_color - if self.error_color == [0, 0, 0, 0] - else self.error_color - ) - if self.max_length_text_color == [0, 0, 0, 0] or updated: - self.max_length_text_color = ( - self.theme_cls.disabled_hint_text_color - if self.max_length_text_color == [0, 0, 0, 0] - else self.max_length_text_color - ) + if texture: + Animation(rgba=color, d=0).start(canvas_group) + a = Animation(color=color, d=0) + if texture is self._hint_text_label: + a.bind(on_complete=update_hint_text_rectangle) + a.start(texture) - self._hint_text_color = self.hint_text_color_normal - self._text_color_normal = self.text_color_normal - self._fill_color = self.fill_color_normal - self._icon_right_color = self.icon_right_color_normal - self._icon_left_color = self.icon_left_color_normal - self._max_length_text_color = [0, 0, 0, 0] - - if self.helper_text_mode in ("on_focus", "on_error"): - self._helper_text_color = [0, 0, 0, 0] - elif self.helper_text_mode == "persistent": - self._helper_text_color = self.helper_text_color_normal - - self._line_color_normal = self.line_color_normal - self._line_color_focus = self.line_color_focus - - def set_notch_rectangle(self, joining: bool = False) -> None: - """ - Animates a notch for the hint text in the rectangle of the text field - of type `rectangle`. - """ - - def on_progress(*args): - self._line_blank_space_right_point = ( - self._hint_text_label.width + dp(17) if not joining else 0 - ) - - if self.hint_text: - animation = Animation( - _line_blank_space_left_point=self._hint_text_label.x - dp(-7) - if not joining - else 0, - duration=0.2, - t="out_quad", - ) - animation.bind(on_progress=on_progress) - animation.start(self) - - def set_active_underline_width(self, width: Union[float, int]) -> None: - """Animates the width of the active underline line.""" - - Animation( - _underline_width=width, - duration=(0.2 if self.line_anim else 0), - t="out_quad", - ).start(self) - - def set_static_underline_color(self, color: list) -> None: - """Animates the color of a static underline line.""" - - Animation( - _line_color_normal=color, - duration=(0.2 if self.line_anim else 0), - t="out_quad", - ).start(self) - - def set_active_underline_color(self, color: list) -> None: - """Animates the fill color for 'fill' mode.""" - - Animation(_line_color_focus=color, duration=0.2, t="out_quad").start( - self - ) - - def set_fill_color(self, color: list) -> None: - """Animates the color of the hint text.""" - - Animation(_fill_color=color, duration=0.2, t="out_quad").start(self) - - def set_helper_text_color(self, color: list) -> None: - """Animates the color of the hint text.""" - - Animation(_helper_text_color=color, duration=0.2, t="out_quad").start( - self - ) - - def set_max_length_text_color(self, color: list) -> None: - """Animates the color of the max length text.""" - - Animation( - _max_length_text_color=color, duration=0.2, t="out_quad" - ).start(self) - - def set_icon_right_color(self, color: list) -> None: - """Animates the color of the icon right.""" - - Animation(_icon_right_color=color, duration=0.2, t="out_quad").start( - self - ) - - def set_icon_left_color(self, color: list) -> None: - """Animates the color of the icon left.""" - - Animation(_icon_left_color=color, duration=0.2, t="out_quad").start( - self - ) - - def set_hint_text_color(self, focus: bool, error: bool = False) -> None: - """Animates the color of the hint text.""" - - if self.mode != "round": - Animation( - _hint_text_color=( - self.hint_text_color_normal - if not focus - else self.hint_text_color_focus - ) - if not error - else self.error_color, - duration=0.2, - t="out_quad", - ).start(self) - - def set_pos_hint_text(self, y: float, x: float = 12) -> None: + def set_pos_hint_text(self, y: float, x: float) -> None: """Animates the x-axis width and y-axis height of the hint text.""" - if self.mode != "round": - Animation(_hint_y=y, duration=0.2, t="out_quad").start(self) - if self.mode == "rectangle": - if not self.icon_left: - _hint_x = x - else: - if y == dp(10): - _hint_x = dp(-4) - else: - _hint_x = dp(20) + Animation(_hint_y=y, _hint_x=x, d=0.2, t="out_quad").start(self) - Animation( - _hint_x=_hint_x, - duration=0.2, - t="out_quad", - ).start(self) - elif self.mode == "fill": - Animation( - _hint_x=dp(16) if not self.icon_left else dp(36), - duration=0.2, - t="out_quad", - ).start(self) - elif self.mode == "line": - Animation( - _hint_x=dp(0) if not self.icon_left else dp(36), - duration=0.2, - t="out_quad", - ).start(self) - - def set_hint_text_font_size(self, font_size: float) -> None: + def set_hint_text_font_size(self) -> None: """Animates the font size of the hint text.""" - if self.mode != "round": - Animation( - _hint_text_font_size=font_size, duration=0.2, t="out_quad" - ).start(self) + Animation( + size=self._hint_text_label.texture_size, d=0.2, t="out_quad" + ).start(self.canvas.after.get_group("hint-text-rectangle")[0]) + + def set_space_in_line( + self, left_width: float | int, right_width: float | int + ) -> None: + """ + Animates the length of the right line of the text field for the + hint text. + """ + + Animation(_left_x_axis_pos=left_width, d=0.2, t="out_quad").start(self) + Animation(_right_x_axis_pos=right_width, d=0.2, t="out_quad").start( + self + ) def set_max_text_length(self) -> None: - """Called when text is entered into a text field.""" + """ + Fired when text is entered into a text field. + Set max length text and updated max length texture. + """ - if self.max_text_length: + if self._max_length_label: + self._max_length_label.text = "" self._max_length_label.text = ( - f"{len(self.text)}/{self.max_text_length}" + f"{len(self.text)}/{self._max_length_label.max_text_length}" + ) + self._max_length_label.texture_update() + max_length_rect = self.canvas.before.get_group("max-length-rect")[0] + max_length_rect.texture = None + max_length_rect.texture = self._max_length_label.texture + max_length_rect.size = self._max_length_label.texture_size + max_length_rect.pos = ( + (self.x + self.width) + - (self._max_length_label.texture_size[0] + dp(16)), + self.y - dp(18), ) - def check_text(self, interval: Union[float, int]) -> None: - self.set_text(self, self.text) + def set_text(self, instance, text: str) -> None: + """Fired when text is entered into a text field.""" - def set_text(self, instance_text_field, text: str) -> None: - """Called when text is entered into a text field.""" + def set_text(*args): + self.text = re.sub("\n", " ", text) if not self.multiline else text + self.set_max_text_length() - self.text = re.sub("\n", " ", text) if not self.multiline else text - self.set_max_text_length() - if self.validator and self.validator == "phone": - pass - # self.format(self.text) + if self.text and self._get_has_error() or self._get_has_error(): + self.error = True + elif self.text and not self._get_has_error(): + self.error = False - if (self.text and self.max_length_text_color) or self._get_has_error(): - self.error = True - if ( - self.text - and self.max_length_text_color - and not self._get_has_error() - ): - self.error = False + # Start the appropriate texture animations when programmatically + # pasting text into a text field. + if len(self.text) != 0 and not self.focus: + if self._hint_text_label: + self._hint_text_label.font_size = theme_font_styles[ + self._hint_text_label.font_style + ]["small"]["font-size"] + self._hint_text_label.texture_update() + self.set_hint_text_font_size() - # Start the appropriate texture animations when programmatically - # pasting text into a text field. - if len(self.text) != 0 and not self.focus: - self.set_pos_hint_text( - (dp(28) if self.mode != "line" else dp(18)) - if self.mode != "rectangle" - else dp(10) - ) + if (not self.text and not self.focus) or ( + self.text and not self.focus + ): + self.on_focus(instance, False) - self.set_hint_text_font_size(sp(12)) - if self.mode == "rectangle": - self.set_notch_rectangle() + set_text() - if (not self.text and not self.focus) or (self.text and not self.focus): - self.on_focus(instance_text_field, False) - - if self.mode == "round" and self.text: - self.hint_text = "" - if self.mode == "round" and not self.text: - self.hint_text = self.__hint_text - - def set_x_pos(self): - pass - - def set_objects_labels(self) -> None: - """ - Creates labels objects for the parameters`helper_text`,`hint_text`, - etc. - """ - - self._helper_text_label = TextfieldLabel( - font_style="Caption", - halign="left", - valign="middle", - field=self, - font_name=self.font_name_helper_text, - ) - self._max_length_label = TextfieldLabel( - font_style="Caption", - halign="right", - valign="middle", - text="", - field=self, - ) - self._hint_text_label = TextfieldLabel( - font_style="Subtitle1", halign="left", valign="middle", field=self - ) - self._icon_right_label = MDIcon(theme_text_color="Custom") - self._icon_left_label = MDIcon(theme_text_color="Custom") - - def on_helper_text(self, instance_text_field, helper_text: str) -> None: - self._helper_text_label.text = helper_text - - def on_focus(self, instance_text_field, focus: bool) -> None: - # TODO: See `cancel_all_animations_on_double_click` method. - # self.cancel_all_animations_on_double_click() + def on_focus(self, instance, focus: bool) -> None: + """Fired when the `focus` value changes.""" if focus: - if self.mode == "rectangle": - self.set_notch_rectangle() - self.set_static_underline_color([0, 0, 0, 0]) - if ( - self.helper_text_mode in ("on_focus", "persistent") - and self.helper_text + if self.mode == "filled": + Animation(_indicator_height=dp(1.25), d=0).start(self) + else: + Animation(_outline_height=dp(1.25), d=0).start(self) + + if self._trailing_icon: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._trailing_icon, + self.canvas.before.get_group("trailing-icons-color")[0], + ( + self.theme_cls.onSurfaceVariantColor + if self._trailing_icon.theme_icon_color == "Primary" + or not self._trailing_icon.icon_color_focus + else self._trailing_icon.icon_color_focus + ) + if not self.error + else self._get_error_color(), + ) + ) + if self._leading_icon: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._leading_icon, + self.canvas.before.get_group("leading-icons-color")[0], + self.theme_cls.onSurfaceVariantColor + if self._leading_icon.theme_icon_color == "Primary" + or not self._leading_icon.icon_color_focus + else self._leading_icon.icon_color_focus, + ) + ) + if self._max_length_label and not self.error: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._max_length_label, + self.canvas.before.get_group("max-length-color")[0], + self.theme_cls.onSurfaceVariantColor + if not self._max_length_label.text_color_focus + else self._max_length_label.text_color_focus, + ) + ) + + if self._helper_text_label and self._helper_text_label.mode in ( + "on_focus", + "persistent", ): Clock.schedule_once( - lambda x: self.set_helper_text_color( - self.helper_text_color_focus - ) - ) - if self.mode == "fill": - Clock.schedule_once( - lambda x: self.set_fill_color(self.fill_color_focus) - ) - self.set_active_underline_width(self.width) - - self.set_pos_hint_text( - (dp(28) if self.mode != "line" else dp(18)) - if self.mode != "rectangle" - else dp(10) - ) - Clock.schedule_once(lambda x: self.set_hint_text_color(focus)) - self.set_hint_text_font_size(sp(12)) - - if self.max_text_length: - Clock.schedule_once( - lambda x: self.set_max_length_text_color( - self.max_length_text_color - ) - ) - if self.icon_right: - Clock.schedule_once( - lambda x: self.set_icon_right_color( - self.icon_right_color_focus - ) - ) - if self.icon_left: - Clock.schedule_once( - lambda x: self.set_icon_left_color( - self.icon_left_color_focus - ) - ) - - if self.error: - if self.hint_text: - Clock.schedule_once( - lambda x: self.set_hint_text_color(focus, self.error) - ) - if self.helper_text: - Clock.schedule_once( - lambda x: self.set_helper_text_color(self.error_color) - ) - if self.max_text_length: - Clock.schedule_once( - lambda x: self.set_max_length_text_color( - self.error_color + lambda x: self.set_texture_color( + self._helper_text_label, + self.canvas.before.get_group("helper-text-color")[0], + ( + self.theme_cls.onSurfaceVariantColor + if not self._helper_text_label.text_color_focus + else self._helper_text_label.text_color_focus ) + if not self.error + else self._get_error_color(), ) - if self.icon_right: - Clock.schedule_once( - lambda x: self.set_icon_right_color(self.error_color) + ) + if ( + self._helper_text_label + and self._helper_text_label.mode == "on_error" + and not self.error + ): + Clock.schedule_once( + lambda x: self.set_texture_color( + self._helper_text_label, + self.canvas.before.get_group("helper-text-color")[0], + self.theme_cls.transparentColor, ) - if self.icon_left: - Clock.schedule_once( - lambda x: self.set_icon_left_color(self.error_color) + ) + if self._hint_text_label: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._hint_text_label, + self.canvas.after.get_group("hint-text-color")[0], + ( + self.theme_cls.primaryColor + if not self._hint_text_label.text_color_focus + else self._hint_text_label.text_color_focus + ) + if not self.error + else self._get_error_color(), + ) + ) + self.set_pos_hint_text( + 0 if self.mode != "outlined" else dp(-14), + ( + -( + ( + self._leading_icon.texture_size[0] + if self._leading_icon + else 0 + ) + + dp(12) + ) + if self._leading_icon + else 0 + ) + if self.mode == "outlined" + else -( + ( + self._leading_icon.texture_size[0] + if self._leading_icon + else 0 + ) + - dp(24) + ), + ) + self._hint_text_label.font_size = theme_font_styles[ + self._hint_text_label.font_style + ]["small"]["font-size"] + self._hint_text_label.texture_update() + self.set_hint_text_font_size() + if self.mode == "outlined": + self.set_space_in_line( + dp(14), self._hint_text_label.texture_size[0] + dp(18) ) else: - if self.helper_text_mode == "persistent" and self.helper_text: - Clock.schedule_once( - lambda x: self.set_helper_text_color( - self.helper_text_color_normal - ) - ) - if self.helper_text_mode == "on_focus" and self.helper_text: - Clock.schedule_once( - lambda x: self.set_helper_text_color([0.0, 0.0, 0.0, 0.0]) - ) - if self.mode == "rectangle" and not self.text: - self.set_notch_rectangle(joining=True) - if not self.text: - if self.mode == "rectangle": - y = dp(38) - elif self.mode == "fill": - y = dp(46) - else: - y = dp(34) - - self.set_pos_hint_text(y) - self.set_hint_text_font_size(sp(16)) - if self.icon_right and not self.error: - Clock.schedule_once( - lambda x: self.set_icon_right_color( - self.icon_right_color_normal - ) - ) - if self.icon_left and not self.error: - Clock.schedule_once( - lambda x: self.set_icon_left_color( - self.icon_left_color_normal - ) - ) - if self.hint_text: - Clock.schedule_once( - lambda x: self.set_hint_text_color(focus, self.error) - ) - - self.set_active_underline_width(0) - Clock.schedule_once( - lambda x: self.set_max_length_text_color([0, 0, 0, 0]) - ) - - if self.mode == "fill": - Clock.schedule_once( - lambda x: self.set_fill_color(self.fill_color_normal) - ) - - self.error = self._get_has_error() or self.error - if self.error: - self.set_static_underline_color(self.error_color) + if self.mode == "filled": + Animation(_indicator_height=dp(1), d=0).start(self) else: + Animation(_outline_height=dp(1), d=0).start(self) + + if self._leading_icon: Clock.schedule_once( - lambda x: self.set_static_underline_color( - self.line_color_normal + lambda x: self.set_texture_color( + self._leading_icon, + self.canvas.before.get_group("leading-icons-color")[0], + self.theme_cls.onSurfaceVariantColor + if self._leading_icon.theme_icon_color == "Primary" + or not self._leading_icon.icon_color_normal + else self._leading_icon.icon_color_normal, + ) + ) + if self._trailing_icon: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._trailing_icon, + self.canvas.before.get_group("trailing-icons-color")[0], + ( + self.theme_cls.onSurfaceVariantColor + if self._trailing_icon.theme_icon_color == "Primary" + or not self._trailing_icon.icon_color_normal + else self._trailing_icon.icon_color_normal + ) + if not self.error + else self._get_error_color(), + ) + ) + if self._max_length_label and not self.error: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._max_length_label, + self.canvas.before.get_group("max-length-color")[0], + self.theme_cls.onSurfaceVariantColor + if not self._max_length_label.text_color_normal + else self._max_length_label.text_color_normal, + ) + ) + if ( + self._helper_text_label + and self._helper_text_label.mode == "on_focus" + ): + Clock.schedule_once( + lambda x: self.set_texture_color( + self._helper_text_label, + self.canvas.before.get_group("helper-text-color")[0], + self.theme_cls.transparentColor, + ) + ) + elif ( + self._helper_text_label + and self._helper_text_label.mode == "persistent" + ): + Clock.schedule_once( + lambda x: self.set_texture_color( + self._helper_text_label, + self.canvas.before.get_group("helper-text-color")[0], + ( + self.theme_cls.onSurfaceVariantColor + if not self._helper_text_label.text_color_normal + else self._helper_text_label.text_color_normal + ) + if not self.error + else self._get_error_color(), ) ) - def on_icon_left(self, instance_text_field, icon_name: str) -> None: - self._icon_left_label.icon = icon_name + if not self.text: + if self._hint_text_label: + if self.mode == "outlined": + self.set_space_in_line(dp(32), dp(32)) + self._hint_text_label.font_size = theme_font_styles[ + self._hint_text_label.font_style + ]["large"]["font-size"] + self._hint_text_label.texture_update() + self.set_hint_text_font_size() + self.set_pos_hint_text( + (self.height / 2) + - (self._hint_text_label.texture_size[1] / 2), + 0, + ) + else: + if self._hint_text_label: + if self.mode == "outlined": + self.set_space_in_line( + dp(14), + self._hint_text_label.texture_size[0] + dp(18), + ) + self.set_pos_hint_text( + 0 if self.mode != "outlined" else dp(-14), + ( + -( + ( + self._leading_icon.texture_size[0] + if self._leading_icon + else 0 + ) + + dp(12) + ) + if self._leading_icon + else 0 + ) + if self.mode == "outlined" + else -( + ( + self._leading_icon.texture_size[0] + if self._leading_icon + else 0 + ) + - dp(24) + ), + ) - def on_icon_right(self, instance_text_field, icon_name: str) -> None: - self._icon_right_label.icon = icon_name + if self._hint_text_label: + Clock.schedule_once( + lambda x: self.set_texture_color( + self._hint_text_label, + self.canvas.after.get_group("hint-text-color")[0], + ( + self.theme_cls.onSurfaceVariantColor + if not self._hint_text_label.text_color_normal + else self._hint_text_label.text_color_normal + ) + if not self.error + else self._get_error_color(), + ), + ) - def on_disabled(self, instance_text_field, disabled_value: bool) -> None: - pass + def on_disabled(self, instance, disabled: bool) -> None: + """Fired when the `disabled` value changes.""" - def on_error(self, instance_text_field, error: bool) -> None: + super().on_disabled(instance, disabled) + + def on_disabled(*args): + if disabled: + self._set_disabled_colors() + else: + self._set_enabled_colors() + + Clock.schedule_once(on_disabled, 0.2) + + def on_error(self, instance, error: bool) -> None: """ Changes the primary colors of the text box to match the `error` value (text field is in an error state or not). """ if error: - Clock.schedule_once( - lambda x: self.set_max_length_text_color(self.error_color) - ) - self.set_active_underline_color(self.error_color) - if self.hint_text: - self.set_hint_text_color(self.focus, self.error) - if self.helper_text: + if self._max_length_label: Clock.schedule_once( - lambda x: self.set_helper_text_color(self.error_color) + lambda x: self.set_texture_color( + self._max_length_label, + self.canvas.before.get_group("max-length-color")[0], + self._get_error_color(), + ) ) - if self.icon_right: + if self._hint_text_label: Clock.schedule_once( - lambda x: self.set_icon_right_color(self.error_color) + lambda x: self.set_texture_color( + self._hint_text_label, + self.canvas.after.get_group("hint-text-color")[0], + self._get_error_color(), + ), ) - if self.icon_left: + if self._helper_text_label and self._helper_text_label.mode in ( + "persistent", + "on_error", + ): Clock.schedule_once( - lambda x: self.set_icon_left_color(self.error_color) + lambda x: self.set_texture_color( + self._helper_text_label, + self.canvas.before.get_group("helper-text-color")[0], + self._get_error_color(), + ) ) - if self.helper_text_mode == "on_error": + if self._trailing_icon: Clock.schedule_once( - lambda x: self.set_helper_text_color(self.error_color) + lambda x: self.set_texture_color( + self._trailing_icon, + self.canvas.before.get_group("trailing-icons-color")[0], + self._get_error_color(), + ) ) else: - Clock.schedule_once( - lambda x: self.set_max_length_text_color( - self.max_length_text_color - ) - ) - self.set_active_underline_color(self.line_color_focus) - if self.hint_text: - self.set_hint_text_color(self.focus) - if self.helper_text: - Clock.schedule_once( - lambda x: self.set_helper_text_color( - self.helper_text_color_focus - ) - ) - if self.icon_right: - Clock.schedule_once( - lambda x: self.set_icon_right_color( - self.icon_right_color_focus - ) - ) - if self.icon_left: - Clock.schedule_once( - lambda x: self.set_icon_left_color( - self.icon_left_color_focus - ) - ) - if self.helper_text_mode == "persistent": - Clock.schedule_once( - lambda x: self.set_helper_text_color( - self.helper_text_color_normal - ) - ) + self.on_focus(self, self.focus) - def on_hint_text(self, instance_text_field, hint_text: str) -> None: - if hint_text: - self.__hint_text = hint_text - self._hint_text_label.text = hint_text - self._hint_text_label.font_size = sp(16) - - def on_width(self, instance_text_field, width: float) -> None: - """Called when the application window is resized.""" - - if self.focus: - self._underline_width = self.width - - def on_height(self, instance_text_field, value_height: float) -> None: + def on_height(self, instance, value_height: float) -> None: if value_height >= self.max_height and self.max_height: self.height = self.max_height - def on_text_color_normal( - self, instance_text_field, color: Union[list, str] - ) -> None: - self._text_color_normal = color + def _set_enabled_colors(self): + def schedule_set_texture_color(widget, group_name, color): + Clock.schedule_once( + lambda x: self.set_texture_color(widget, group_name, color) + ) - def on_hint_text_color_normal( - self, instance_text_field, color: Union[list, str] - ) -> None: - self._hint_text_color = color + max_length_label_group = self.canvas.before.get_group( + "max-length-color" + ) + helper_text_label_group = self.canvas.before.get_group( + "helper-text-color" + ) + hint_text_label_group = self.canvas.after.get_group("hint-text-color") + leading_icon_group = self.canvas.before.get_group("leading-icons-color") + trailing_icon_group = self.canvas.before.get_group( + "trailing-icons-color" + ) - def on_helper_text_color_normal( - self, instance_text_field, color: Union[list, str] - ) -> None: - self._helper_text_color = color + error_color = self._get_error_color() + on_surface_variant_color = self.theme_cls.onSurfaceVariantColor - def on_icon_right_color_normal( - self, instance_text_field, color: Union[list, str] - ) -> None: - self._icon_right_color = color + if self._max_length_label: + schedule_set_texture_color( + self._max_length_label, + max_length_label_group[0], + self._max_length_label.color[:-1] + [1] + if not self.error + else error_color, + ) + if self._helper_text_label: + schedule_set_texture_color( + self._helper_text_label, + helper_text_label_group[0], + on_surface_variant_color + if not self._helper_text_label.text_color_focus + else self._helper_text_label.text_color_focus + if not self.error + else error_color, + ) + if self._hint_text_label: + schedule_set_texture_color( + self._hint_text_label, + hint_text_label_group[0], + on_surface_variant_color + if not self._hint_text_label.text_color_normal + else self._hint_text_label.text_color_normal + if not self.error + else error_color, + ) + if self._leading_icon: + schedule_set_texture_color( + self._leading_icon, + leading_icon_group[0], + on_surface_variant_color + if self._leading_icon.theme_icon_color == "Primary" + or not self._leading_icon.icon_color_normal + else self._leading_icon.icon_color_normal, + ) + if self._trailing_icon: + schedule_set_texture_color( + self._trailing_icon, + trailing_icon_group[0], + on_surface_variant_color + if self._trailing_icon.theme_icon_color == "Primary" + or not self._trailing_icon.icon_color_normal + else self._trailing_icon.icon_color_normal + if not self.error + else error_color, + ) - def on_line_color_normal( - self, instance_text_field, color: Union[list, str] - ) -> None: - self._line_color_normal = color + def _set_disabled_colors(self): + def schedule_set_texture_color(widget, group_name, color, opacity): + Clock.schedule_once( + lambda x: self.set_texture_color( + widget, group_name, color + [opacity] + ) + ) - def on_max_length_text_color( - self, instance_text_field, color: Union[list, str] - ) -> None: - self._max_length_text_color = color + max_length_label_group = self.canvas.before.get_group( + "max-length-color" + ) + helper_text_label_group = self.canvas.before.get_group( + "helper-text-color" + ) + hint_text_label_group = self.canvas.after.get_group("hint-text-color") + leading_icon_group = self.canvas.before.get_group("leading-icons-color") + trailing_icon_group = self.canvas.before.get_group( + "trailing-icons-color" + ) - def _set_color(self, attr_name: str, color: str, updated: bool) -> None: - if attr_name in self._colors_to_updated or updated: - if attr_name in self._colors_to_updated: - setattr(self, attr_name, color) + disabled_color = self.theme_cls.disabledTextColor[:-1] - def _set_attr_names_to_updated(self, interval: Union[float, int]) -> None: - """ - Sets and update the default color dictionary for text field textures. - """ - - self._attr_names_to_updated = { - "line_color_normal": self.theme_cls.disabled_hint_text_color, - "line_color_focus": self.theme_cls.primary_color, - "hint_text_color_normal": self.theme_cls.disabled_hint_text_color, - "hint_text_color_focus": self.theme_cls.primary_color, - "helper_text_color_normal": self.theme_cls.disabled_hint_text_color, - "helper_text_color_focus": self.theme_cls.disabled_hint_text_color, - "text_color_normal": self.theme_cls.disabled_hint_text_color, - "text_color_focus": self.theme_cls.primary_color, - "fill_color_normal": self.theme_cls.bg_darkest, - "fill_color_focus": self.theme_cls.bg_dark, - "icon_right_color_normal": self.theme_cls.disabled_hint_text_color, - "icon_right_color_focus": self.theme_cls.primary_color, - "icon_left_color_normal": self.theme_cls.disabled_hint_text_color, - "icon_left_color_focus": self.theme_cls.primary_color, - } + if self._max_length_label: + schedule_set_texture_color( + self._max_length_label, + max_length_label_group[0], + disabled_color, + self.text_field_opacity_value_disabled_max_length_label, + ) + if self._helper_text_label: + schedule_set_texture_color( + self._helper_text_label, + helper_text_label_group[0], + disabled_color, + self.text_field_opacity_value_disabled_helper_text_label, + ) + if self._hint_text_label: + schedule_set_texture_color( + self._hint_text_label, + hint_text_label_group[0], + disabled_color, + self.text_field_opacity_value_disabled_hint_text_label, + ) + if self._leading_icon: + schedule_set_texture_color( + self._leading_icon, + leading_icon_group[0], + disabled_color, + self.text_field_opacity_value_disabled_leading_icon, + ) + if self._trailing_icon: + schedule_set_texture_color( + self._trailing_icon, + trailing_icon_group[0], + disabled_color, + self.text_field_opacity_value_disabled_trailing_icon, + ) def _get_has_error(self) -> bool: """ @@ -1890,7 +1812,10 @@ class MDTextField( "time": self.is_time_valid, }[self.validator](self.text) return has_error - if self.max_text_length and len(self.text) > self.max_text_length: + if ( + self._max_length_label + and len(self.text) > self._max_length_label.max_text_length + ): has_error = True else: if all((self.required, len(self.text) == 0)): @@ -1899,91 +1824,15 @@ class MDTextField( has_error = False return has_error + def _get_error_color(self): + return ( + self.theme_cls.errorColor + if not self.error_color + else self.error_color + ) + + def _check_text(self, *args) -> None: + self.set_text(self, self.text) + def _refresh_hint_text(self): """Method override to avoid duplicate hint text texture.""" - - -if __name__ == "__main__": - from kivy.lang import Builder - from kivy.uix.textinput import TextInput - - from kivymd.app import MDApp - - KV = """ -MDScreen: - - MDScrollView: - - MDList: - id: box - spacing: "32dp" - padding: "56dp", "12dp", "56dp", "12dp" - - MDTextField: - hint_text: "Label" - helper_text: "Error message" - mode: "rectangle" - max_text_length: 5 - - MDTextField: - icon_left: "git" - hint_text: "Label" - helper_text: "Error message" - mode: "rectangle" - - MDTextField: - icon_left: "git" - hint_text: "Label" - helper_text: "Error message" - mode: "fill" - - MDTextField: - hint_text: "Label" - helper_text: "Error message" - mode: "fill" - - MDTextField: - hint_text: "Label" - helper_text: "Error message" - - MDTextField: - icon_left: "git" - hint_text: "Label" - helper_text: "Error message" - - MDTextField: - hint_text: "Round mode" - mode: "round" - max_text_length: 15 - helper_text: "Message" - - MDTextField: - hint_text: "Date dd/mm/yyyy in [01/01/1900, 01/01/2100] interval" - helper_text: "Enter a valid dd/mm/yyyy date" - validator: "date" - date_format: "dd/mm/yyyy" - date_interval: "01/01/1900", "01/01/2100" - - MDTextField: - hint_text: "Email" - helper_text: "user@gmail.com" - validator: "email" - - MDFlatButton: - text: "SET TEXT" - pos_hint: {"center_x": .5} - on_release: app.set_text() -""" - - class Test(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def set_text(self): - for widget in self.root.ids.box.children: - if issubclass(widget.__class__, TextInput): - widget.text = "Input text" - - Test().run() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__init__.py deleted file mode 100644 index e4f163e..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# NOQA F401 -from .toolbar import ( - MDActionBottomAppBarButton, - MDActionOverFlowButton, - MDBottomAppBar, - MDFabBottomAppBarButton, - MDTopAppBar, -) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 0e458cd..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__pycache__/toolbar.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__pycache__/toolbar.cpython-311.pyc deleted file mode 100644 index 221bf7d..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/__pycache__/toolbar.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/toolbar.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/toolbar.kv deleted file mode 100644 index b5538af..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/toolbar.kv +++ /dev/null @@ -1,113 +0,0 @@ - - - IconLeftWidget: - icon: root.icon - - - - size_hint_y: None - padding: [root.theme_cls.horizontal_margins - dp(12), 0] - elevation: root.elevation - - canvas: - Color: - rgba: - ( \ - root.theme_cls.primary_color \ - if root.md_bg_color == [0, 0, 0, 0] \ - else root.md_bg_color \ - ) \ - if root.type == "top" else \ - ( \ - ( \ - root.theme_cls.primary_color \ - if not self.md_bg_bottom_color else \ - self.md_bg_bottom_color \ - ) \ - if root.parent and root.parent.md_bg_color == [0, 0, 0, 0] \ - else \ - ( \ - root.parent.md_bg_color if root.parent else root.md_bg_color \ - ) \ - ) - Mesh: - vertices: root._vertices_left - indices: root._indices_left - mode: "triangle_fan" - - Mesh: - vertices: root._vertices_right - indices: root._indices_right - mode: "triangle_fan" - - RoundedRectangle: - pos: root._rectangle_left_pos - size: root._rectangle_left_width, root._rounded_rectangle_height - radius: - [0,] if root.mode == "normal" \ - else [0, root.notch_radius * root._rounding_percentage, 0, 0] - - RoundedRectangle: - pos: root._rectangle_right_pos - size: root._rectangle_right_width, root._rounded_rectangle_height - radius: - [0,] if root.mode == "normal" \ - else [root.notch_radius * root._rounding_percentage, 0, 0, 0] - - - - orientation: "vertical" - - MDBoxLayout: - padding: 0, 0, 0, root.height - headline_box.height - (dp(48) + dp(20)) - - MDBoxLayout: - id: left_actions - orientation: "horizontal" - size_hint_x: None - padding: [0, (self.height - dp(48)) / 2] - - MDLabel: - id: label_title - font_style: "H6" - opposite_colors: root.opposite_colors - theme_text_color: "Custom" if not root.opposite_colors else "Primary" - text_color: root.specific_text_color - text: root.title - shorten: True - shorten_from: "right" - markup: True - padding: dp(12), 0 - halign: - root.anchor_title \ - if root.anchor_title else \ - root.update_anchor_title(app.theme_cls.material_style) - - MDBoxLayout: - id: right_actions - orientation: "horizontal" - adaptive_width: True - padding: [0, (self.height - dp(48)) / 2] - - MDBoxLayout: - id: headline_box - size_hint_y: None - height: label_headline.texture_size[1] if label_headline.text else 0 - padding: "16dp" - - MDLabel: - id: label_headline - adaptive_height: True - shorten: True - shorten_from: "right" - theme_text_color: "Custom" - text_color: - label_title.text_color \ - if not root.headline_text_color else \ - root.headline_text_color - text: - root.headline_text \ - if root.type_height in ("medium", "large") \ - and app.theme_cls.material_style == "M3" \ - and root.type != "bottom" else \ - "" diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/toolbar.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/toolbar.py deleted file mode 100644 index 091b040..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/toolbar/toolbar.py +++ /dev/null @@ -1,2225 +0,0 @@ -""" -Components/Toolbar -================== - -.. seealso:: - - `Material Design spec, App bars: top `_ - - `Material Design spec, App bars: bottom `_ - - `Material Design 3 spec, App bars: top `_ - - `Material Design 3 spec, App bars: bottom `_ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-top.png - :align: center - -`KivyMD` provides the following bar positions for use: - -- TopAppBar_ -- BottomAppBar_ - -.. TopAppBar_: -TopAppBar ---------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDBoxLayout: - orientation: "vertical" - md_bg_color: "#1E1E15" - - MDTopAppBar: - title: "MDTopAppBar" - - MDLabel: - text: "Content" - halign: "center" - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-1.png - :align: center - -Add left menu -------------- - -.. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - anchor_title: "left" - left_action_items: [["menu", lambda x: app.callback()]] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-2.png - :align: center - -.. note:: - - The callback is optional. ``left_action_items: [["menu"]]`` would also work for a button that does nothing. - -Add right menu --------------- - -.. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - anchor_title: "left" - right_action_items: [["dots-vertical", lambda x: app.callback()]] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-3.png - :align: center - -Add two item to the right menu ------------------------------- - -.. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - anchor_title: "left" - right_action_items: - [ - ["dots-vertical", lambda x: app.callback_1()], - ["clock", lambda x: app.callback_2()] - ] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-4.png - :align: center - -Change bar color ----------------- - -.. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - anchor_title: "left" - md_bg_color: "brown" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-5.png - :align: center - -Change bar text color ---------------------- - -.. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - anchor_title: "left" - specific_text_color: "white" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-6.png - :align: center - -Shadow elevation control ------------------------- - -.. code-block:: kv - - MDTopAppBar: - title: "Elevation 4" - anchor_title: "left" - elevation: 4 - shadow_color: "brown" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-7.png - :align: center - -.. BottomAppBar: -BottomAppBar ------------- - -M2 style bottom app bar ------------------------ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-bottom.png - :align: center - -Usage ------ - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDBoxLayout: - md_bg_color: "#1E1E15" - - # Will always be at the bottom of the screen. - MDBottomAppBar: - - MDTopAppBar: - title: "MDBottomAppBar" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.material_style = "M2" - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-8.png - :align: center - -Event on floating button ------------------------- - -Event ``on_action_button``: - -.. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - title: "MDBottomAppBar" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - on_action_button: app.callback(self.icon) - -Floating button position ------------------------- - -Mode: - -- `'free-end'` -- `'free-center'` -- `'end'` -- `'center'` - -.. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - title: "MDBottomAppBar" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - mode: "end" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-9.png - :align: center - -.. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - title: "MDBottomAppBar" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - mode: "free-end" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-10.png - :align: center - -Custom color ------------- - -.. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - title: "MDBottomAppBar" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - icon_color: 0, 1, 0, 1 - md_bg_bottom_color: "brown" - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-11.png - :align: center - -M3 style bottom app bar ------------------------ - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/app-bar-bottom-m3.png - :align: center - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - MDFloatLayout: - md_bg_color: "#151511" - - MDBottomAppBar: - md_bg_color: "#232217" - icon_color: "#8A8D79" - - MDFabBottomAppBarButton: - icon: "plus" - md_bg_color: "#373A22" - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-1.png - :align: center - -Add action items ----------------- - -.. code-block:: kv - - #:import MDActionBottomAppBarButton kivymd.uix.toolbar.MDActionBottomAppBarButton - - - MDFloatLayout: - - MDBottomAppBar: - action_items: - [ - MDActionBottomAppBarButton(icon="gmail"), - MDActionBottomAppBarButton(icon="label-outline"), - MDActionBottomAppBarButton(icon="bookmark"), - ] - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-2.png - :align: center - -Change action items -------------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - - KV = ''' - #:import MDActionBottomAppBarButton kivymd.uix.toolbar.MDActionBottomAppBarButton - - - MDFloatLayout: - md_bg_color: "#151511" - - MDBottomAppBar: - id: bottom_appbar - md_bg_color: "#232217" - icon_color: "#8A8D79" - action_items: - [ - MDActionBottomAppBarButton(icon="gmail"), - MDActionBottomAppBarButton(icon="bookmark"), - ] - - MDFabBottomAppBarButton: - icon: "plus" - md_bg_color: "#373A22" - on_release: app.change_actions_items() - ''' - - - class Example(MDApp): - def change_actions_items(self): - self.root.ids.bottom_appbar.action_items = [ - MDActionBottomAppBarButton(icon="magnify"), - MDActionBottomAppBarButton(icon="trash-can-outline"), - MDActionBottomAppBarButton(icon="download-box-outline"), - ] - - def build(self): - self.theme_cls.theme_style = "Dark" - return Builder.load_string(KV) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-3.gif - :align: center - -A practical example -------------------- - -.. code-block:: python - - from kivy.clock import Clock - from kivy.lang import Builder - from kivy.properties import StringProperty, BooleanProperty, ObjectProperty - from kivy.uix.behaviors import FocusBehavior - from kivy.uix.recycleboxlayout import RecycleBoxLayout - from kivy.uix.recycleview.layout import LayoutSelectionBehavior - from kivy.uix.recycleview.views import RecycleDataViewBehavior - - from kivymd.uix.boxlayout import MDBoxLayout - from kivymd.uix.toolbar import MDActionBottomAppBarButton - from kivymd.app import MDApp - from kivymd.utils import asynckivy - - from faker import Faker # pip install Faker - - KV = ''' - #:import MDFabBottomAppBarButton kivymd.uix.toolbar.MDFabBottomAppBarButton - - - - orientation: "vertical" - adaptive_height: True - md_bg_color: "#373A22" if self.selected else "#1F1E15" - radius: 16 - padding: 0, 0, 0, "16dp" - - TwoLineAvatarListItem: - divider: None - _no_ripple_effect: True - text: root.name - secondary_text: root.time - theme_text_color: "Custom" - text_color: "#8A8D79" - secondary_theme_text_color: self.theme_text_color - secondary_text_color: self.text_color - - ImageLeftWidget: - source: root.avatar - radius: self.height / 2 - - MDLabel: - text: root.text - adaptive_height: True - theme_text_color: "Custom" - text_color: "#8A8D79" - padding_x: "16dp" - shorten: True - shorten_from: "right" - - Widget: - - - MDFloatLayout: - md_bg_color: "#151511" - - RecycleView: - id: card_list - viewclass: "UserCard" - - SelectableRecycleGridLayout: - orientation: 'vertical' - spacing: "16dp" - padding: "16dp" - default_size: None, dp(120) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - multiselect: True - touch_multiselect: True - - MDBottomAppBar: - id: bottom_appbar - scroll_cls: card_list - allow_hidden: True - md_bg_color: "#232217" - icon_color: "#8A8D79" - - MDFabBottomAppBarButton: - id: fab_button - icon: "plus" - md_bg_color: "#373A22" - ''' - - - class UserCard(RecycleDataViewBehavior, MDBoxLayout): - name = StringProperty() - time = StringProperty() - text = StringProperty() - avatar = StringProperty() - callback = ObjectProperty(lambda x: x) - - index = None - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - - def refresh_view_attrs(self, rv, index, data): - self.index = index - return super().refresh_view_attrs(rv, index, data) - - def on_touch_down(self, touch): - if super().on_touch_down(touch): - return True - if self.collide_point(*touch.pos) and self.selectable: - Clock.schedule_once(self.callback) - return self.parent.select_with_touch(self.index, touch) - - def apply_selection(self, rv, index, is_selected): - self.selected = is_selected - rv.data[index]["selected"] = is_selected - - - class SelectableRecycleGridLayout( - FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout - ): - pass - - - class Test(MDApp): - selected_cards = False - - def build(self): - return Builder.load_string(KV) - - def on_tap_card(self, *args): - datas = [data["selected"] for data in self.root.ids.card_list.data] - if True in datas and not self.selected_cards: - self.root.ids.bottom_appbar.action_items = [ - MDActionBottomAppBarButton(icon="gmail"), - MDActionBottomAppBarButton(icon="label-outline"), - MDActionBottomAppBarButton(icon="bookmark"), - ] - self.root.ids.fab_button.icon = "pencil" - self.selected_cards = True - else: - if len(list(set(datas))) == 1 and not list(set(datas))[0]: - self.selected_cards = False - if not self.selected_cards: - self.root.ids.bottom_appbar.action_items = [ - MDActionBottomAppBarButton(icon="magnify"), - MDActionBottomAppBarButton(icon="trash-can-outline"), - MDActionBottomAppBarButton(icon="download-box-outline"), - ] - self.root.ids.fab_button.icon = "plus" - - def on_start(self): - async def generate_card(): - for i in range(10): - await asynckivy.sleep(0) - self.root.ids.card_list.data.append( - { - "name": fake.name(), - "time": fake.date(), - "avatar": fake.image_url(), - "text": fake.text(), - "selected": False, - "callback": self.on_tap_card, - } - ) - - self.on_tap_card() - fake = Faker() - Clock.schedule_once(lambda x: asynckivy.start(generate_card())) - - - Test().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottom-app-bar-m3-style-4.gif - :align: center - -Tooltips --------- - -You can add MDTooltips to the icons by adding a text string to the bar item, -as shown below: - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.snackbar import Snackbar - - KV = ''' - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "MDTopAppBar" - left_action_items: [["menu", "This is the navigation"]] - right_action_items: - [ - [ - "dots-vertical", - lambda x: app.callback(x), - "this is the More Actions" - ] - ] - - MDLabel: - text: "Content" - halign: "center" - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.material_style = "M2" - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def callback(self, button): - Snackbar(text="Hello World").open() - - - Example().run() - -M3 style top app bar --------------------- - -.. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.toolbar import MDTopAppBar - - KV = ''' - MDScreen: - - MDBoxLayout: - id: box - orientation: "vertical" - spacing: "12dp" - pos_hint: {"top": 1} - adaptive_height: True - ''' - - - class Example(MDApp): - def build(self): - self.theme_cls.theme_style = "Dark" - self.theme_cls.primary_palette = "Orange" - return Builder.load_string(KV) - - def on_start(self): - for type_height in ["medium", "large", "small"]: - self.root.ids.box.add_widget( - MDTopAppBar( - type_height=type_height, - headline_text=f"Headline {type_height.lower()}", - md_bg_color="brown", - left_action_items=[["arrow-left", lambda x: x]], - right_action_items=[ - ["attachment", lambda x: x], - ["calendar", lambda x: x], - ["dots-vertical", lambda x: x], - ], - title="Title" if type_height == "small" else "", - anchor_title="left", - ) - ) - - - Example().run() - -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-m3.png - :align: center -""" - -from __future__ import annotations - -__all__ = ( - "MDTopAppBar", - "MDBottomAppBar", - "MDActionBottomAppBarButton", - "MDFabBottomAppBarButton", - "MDActionOverFlowButton", -) - -import os -from math import cos, radians, sin -from typing import Union - -from kivy import Logger -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.core.window import Window -from kivy.lang import Builder -from kivy.metrics import dp -from kivy.properties import ( - BooleanProperty, - ColorProperty, - ListProperty, - NumericProperty, - ObjectProperty, - OptionProperty, - StringProperty, -) -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.floatlayout import FloatLayout -from kivy.uix.scrollview import ScrollView - -from kivymd import uix_path -from kivymd.color_definitions import text_colors -from kivymd.material_resources import TOP_APP_BAR_ELEVATION -from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import ( - CommonElevationBehavior, - DeclarativeBehavior, - RotateBehavior, - ScaleBehavior, - SpecificBackgroundColorBehavior, -) -from kivymd.uix.button import MDFloatingActionButton, MDIconButton -from kivymd.uix.controllers import WindowController -from kivymd.uix.list import OneLineIconListItem -from kivymd.uix.menu import MDDropdownMenu -from kivymd.uix.tooltip import MDTooltip -from kivymd.utils import asynckivy -from kivymd.utils.set_bars_colors import set_bars_colors - -with open( - os.path.join(uix_path, "toolbar", "toolbar.kv"), encoding="utf-8" -) as kv_file: - Builder.load_string(kv_file.read()) - - -class MDFabBottomAppBarButton( - MDFloatingActionButton, RotateBehavior, ScaleBehavior, MDTooltip -): - """ - Implements a floating action button (FAB) for a bar with type 'bottom'. - - For more information, see in the - :class:`~kivymd.uix.button.MDFloatingActionButton` and - :class:`~kivymd.uix.behaviors.RotateBehavior` and - :class:`~kivymd.uix.behaviors.ScaleBehavior` and - :class:`~kivymd.uix.tooltip.MDTooltip` - classes documentation. - """ - - def set__radius(self, *args) -> None: - super().set__radius() - if self.theme_cls.material_style == "M3": - self.elevation = 0 - - -class ActionTopAppBarButton(MDIconButton, MDTooltip): - """ - Implements action buttons on the bar. - - For more information, see in the - :class:`~kivymd.uix.button.MDIconButton` and - :class:`~kivymd.uix.tooltip.MDTooltip` - classes documentation. - """ - - # The text of the menu item of the corresponding action button that will - # be displayed in the `OverFlowMenu` menu. - overflow_text = StringProperty() - - -class MDActionBottomAppBarButton(ActionTopAppBarButton): - """ - Implements action buttons for a :class:'MDBottomAppBar' class. - - .. versionadded:: 1.2.0 - - For more information, see in the - :class:`~kivymd.uix.button.MDIconButton` and - :class:`~kivymd.uix.tooltip.MDTooltip` - classes documentation. - """ - - -class MDActionOverFlowButton(ActionTopAppBarButton): - """ - Implements a bar action button for the `OverFlowMenu` menu. - - For more information, see in the - :class:`~kivymd.uix.button.MDIconButton` and - :class:`~kivymd.uix.tooltip.MDTooltip` - classes documentation. - """ - - icon = "dots-vertical" - - -class OverFlowMenu(MDDropdownMenu): - """ - Implements a menu for the items (:class:`~OverFlowMenuItem`) of the - corresponding action buttons. - """ - - -class OverFlowMenuItem(OneLineIconListItem): - """Implements a menu (:class:`~OverFlowMenu`) item.""" - - icon = StringProperty() - - -class NotchedBox( - ThemableBehavior, - CommonElevationBehavior, - SpecificBackgroundColorBehavior, - BoxLayout, -): - elevation = NumericProperty(TOP_APP_BAR_ELEVATION) - notch_radius = NumericProperty() - notch_center_x = NumericProperty("100dp") - - _indices_right = ListProperty() - _vertices_right = ListProperty() - _indices_left = ListProperty() - _vertices_left = ListProperty() - _rounded_rectangle_height = NumericProperty("6dp") - _total_angle = NumericProperty(180) - _rectangle_left_pos = ListProperty([0, 0]) - _rectangle_left_width = NumericProperty() - _rectangle_right_pos = ListProperty([0, 0]) - _rectangle_right_width = NumericProperty() - _rounding_percentage = NumericProperty(0.15) - _shift = NumericProperty(dp(4)) - - def __init__(self, **kw): - super().__init__(**kw) - self.bind( - size=self._update_canvas, - pos=self._update_canvas, - notch_radius=self._update_canvas, - notch_center_x=self._update_canvas, - ) - Clock.schedule_once(self._update_canvas) - - def _update_canvas(self, *args): - pos = self.pos - size = [ - self.width, - self.size[1] - self._rounded_rectangle_height / 2, - ] - notch_center_x = self.pos[0] + self.notch_center_x - circle_radius = self.notch_radius - degree_diff = int((180 - self._total_angle) / 2) - circle_center = [notch_center_x, pos[1] + size[1]] - left_circle_pos = self._points_on_circle( - circle_center, circle_radius, 180 + degree_diff, 270 - ) - - self._rectangle_left_pos = [ - pos[0], - pos[1] + size[1] - self._rounded_rectangle_height / 2, - ] - self._rectangle_left_width = left_circle_pos[0][0] - self.pos[0] - - right_circle_pos = self._points_on_circle( - circle_center, circle_radius, -degree_diff, -90 - ) - - self._rectangle_right_pos = [ - right_circle_pos[0][0], - pos[1] + size[1] - self._rounded_rectangle_height / 2, - ] - self._rectangle_right_width = pos[0] + size[0] - right_circle_pos[0][0] - - raw_vertices_left = self._make_vertices( - pos, [notch_center_x - pos[0], size[1]], "left", left_circle_pos - ) - raw_vertices_right = self._make_vertices( - [notch_center_x, pos[1]], - [size[0] + pos[0] - notch_center_x, size[1]], - "right", - right_circle_pos, - ) - - left_vertices, left_indices = self._make_vertices_indices( - raw_vertices_left - ) - right_vertices, right_indices = self._make_vertices_indices( - raw_vertices_right - ) - - self._update_mesh(left_vertices, left_indices, "left") - self._update_mesh(right_vertices, right_indices, "right") - - def _update_mesh(self, vertices, indices, mode): - if mode == "left": - self._indices_left = indices - self._vertices_left = vertices - else: - self._indices_right = indices - self._vertices_right = vertices - return True - - @staticmethod - def _make_vertices_indices(points_list): - vertices = [] - indices = [] - for index, point in enumerate(points_list): - indices.append(index) - vertices.extend([point[0], point[1], 0, 0]) - - return [vertices, indices] - - @staticmethod - def _make_vertices(rectangle_pos, rectangle_size, mode, notch_points=[]): - x = rectangle_pos[0] - y = rectangle_pos[1] - w = rectangle_size[0] - h = rectangle_size[1] - - if mode == "left": - rectangle_vertices = [[x, y], [x, y + h]] - elif mode == "right": - rectangle_vertices = [[x + w, y], [x + w, y + h]] - rectangle_vertices.extend(notch_points) - if mode == "left": - rectangle_vertices.extend([[x + w, y]]) - elif mode == "right": - rectangle_vertices.extend([[x, y]]) - - return rectangle_vertices - - @staticmethod - def _points_on_circle(center, radius, start_angle, end_angle): - points = [] - y_diff = False - if end_angle >= 180: - step = 1 - end_angle += 1 - elif end_angle <= 0: - step = -1 - end_angle -= 1 - else: - raise Exception("Invalid value for start angle") - - for degree in range(start_angle, end_angle, step): - angle = radians(degree) - x = center[0] + (radius * cos(angle)) - y = center[1] + (radius * sin(angle)) - - if y_diff is False: - y_diff = abs(y - center[1]) - - y += y_diff - points.append([x, y]) - - return points - - -class MDTopAppBar(DeclarativeBehavior, NotchedBox, WindowController): - """ - Top app bar class. - - For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~NotchedBox` and - :class:`~kivymd.uix.controllers.WindowController` - classes documentation. - - :Events: - `on_action_button` - Method for the button used for the :class:`~MDBottomAppBar` class. - """ - - left_action_items = ListProperty() - """ - The icons on the left of the bar. - To add one, append a list like the following: - - .. code-block:: kv - - MDTopAppBar: - left_action_items: - ["dots-vertical", callback, "tooltip text", "overflow text"] - - ``icon_name`` - is a string that corresponds to an icon definition: - - .. code-block:: kv - - MDTopAppBar: - right_action_items: [["home"]] - - ``callback`` - is the function called on a touch release event and: - - .. code-block:: kv - - MDTopAppBar: - right_action_items: [["home", lambda x: app.callback(x)]] - - .. code-block:: python - - class Test(MDApp): - def callback(self, instance_action_top_appbar_button): - print(instance_action_top_appbar_button) - - ``tooltip text`` - is the text to be displayed in the tooltip: - - .. code-block:: kv - - MDTopAppBar: - right_action_items: - [ - ["home", lambda x: app.callback(x), "Home"], - ["message-star", lambda x: app.callback(x), "Message star"], - ["message-question", lambda x: app.callback(x), "Message question"], - ["message-reply", lambda x: app.callback(x), "Message reply"], - ] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-tooltip-text.png - :align: center - - ``overflow text`` - is the text for menu items (:class:`~OverFlowMenuItem`) - of the corresponding action buttons: - - .. code-block:: kv - - MDTopAppBar: - use_overflow: True - right_action_items: - [ - ["home", lambda x: x, "", "Home"], - ["message-star", lambda x: x, "", "Message star"], - ["message-question", lambda x: x, "" , "Message question"], - ["message-reply", lambda x: x, "", "Message reply"], - ] - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-overflow-text.png - :align: center - - ``icon color`` - icon color: - - .. code-block:: kv - - MDTopAppBar: - right_action_items: - [ - [ - "dots-vertical", - callback, - "tooltip text", - "overflow text", - (1, 1, 1, 1), - ] - ] - - Both the ``callback`` and ``tooltip text`` and ``overflow text`` and ``icon color`` are - optional but the order must be preserved. - - :attr:`left_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - right_action_items = ListProperty() - """ - The icons on the left of the bar. - Works the same way as :attr:`left_action_items`. - - :attr:`right_action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - title = StringProperty() - """ - Text app bar. - - .. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-title.png - :align: center - - :attr:`title` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - mode = OptionProperty( - "center", options=["free-end", "free-center", "end", "center"] - ) - """ - Floating button position. Only for :class:`~MDBottomAppBar` class. - Available options are: `'free-end'`, `'free-center'`, `'end'`, `'center'`. - - .. rubric:: Mode "end": - - .. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - title: "Title" - icon: "git" - type: "bottom" - left_action_items: [["menu", lambda x: x]] - mode: "end" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-9.png - :align: center - - .. rubric:: Mode "free-end": - - .. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - mode: "free-end" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-10.png - :align: center - - .. rubric:: Mode "free-center": - - .. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - mode: "free-center" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-free-center.png - :align: center - - .. rubric:: Mode "center": - - .. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - mode: "center" - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-center.png - :align: center - - :attr:`mode` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'center'`. - """ - - type = OptionProperty("top", options=["top", "bottom"]) - """ - When using the :class:`~MDBottomAppBar` class, the parameter ``type`` - must be set to `'bottom'`: - - .. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - type: "bottom" - - Available options are: `'top'`, `'bottom'`. - - :attr:`type` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'top'`. - """ - - opposite_colors = BooleanProperty(False) - """ - Changes the color of the label to the color opposite to the main theme. - - .. code-block:: kv - - MDTopAppBar: - opposite_colors: True - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-true.png - :align: center - - .. code-block:: kv - - MDTopAppBar: - opposite_colors: False - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-opposite-false.png - :align: center - """ - - md_bg_bottom_color = ColorProperty(None) - """ - The background color in (r, g, b, a) or string format for the bar with the - ``bottom`` mode. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDBottomAppBar: - - MDTopAppBar: - md_bg_bottom_color: "brown" - icon_color: self.md_bg_bottom_color - - .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/toolbar-md-bg-bottom-color.png - :align: center - - :attr:`md_bg_bottom_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - set_bars_color = BooleanProperty(False) - """ - If `True` the background color of the bar status will be set automatically - according to the current color of the bar. - - .. versionadded:: 1.0.0 - - See `set_bars_colors `_ - for more information. - - :attr:`set_bars_color` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - use_overflow = BooleanProperty(False) - """ - As a top app bar is resized, actions move to the overflow menu from right - to left. - - .. versionadded:: 1.0.0 - - .. code-block:: kv - - MDTopAppBar: - title: "MDTopAppBar" - use_overflow: True - right_action_items: - [ - ["home", lambda x: x, "Home", "Home"], - ["message-star", lambda x: x, "Message star", "Message star"], - ["message-question", lambda x: x, "Message question", "Message question"], - ["message-reply", lambda x: x, "Message reply", "Message reply"], - ] - - :attr:`use_overflow` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - overflow_cls = ObjectProperty() - """ - Must be an object of the :class:`~kivymd.uix.menu.MDDropdownMenu' class. - See :class:`~kivymd.uix.menu.MDDropdownMenu` class documentation for more - information. - - .. versionadded:: 1.0.0 - - .. code-block:: python - - from kivy.lang import Builder - - from kivymd.app import MDApp - from kivymd.uix.menu import MDDropdownMenu - - KV = ''' - #:import CustomOverFlowMenu __main__.CustomOverFlowMenu - - - MDBoxLayout: - orientation: "vertical" - - MDTopAppBar: - title: "MDTopAppBar" - use_overflow: True - overflow_cls: CustomOverFlowMenu() - right_action_items: - [ - ["home", lambda x: x, "Home", "Home"], - ["message-star", lambda x: x, "Message star", "Message star"], - ["message-question", lambda x: x, "Message question", "Message question"], - ["message-reply", lambda x: x, "Message reply", "Message reply"], - ] - - MDLabel: - text: "Content" - halign: "center" - ''' - - - class CustomOverFlowMenu(MDDropdownMenu): - # In this class you can set custom properties for the overflow menu. - pass - - - class Example(MDApp): - def build(self): - return Builder.load_string(KV) - - def callback(self, instance_action_top_appbar_button): - print(instance_action_top_appbar_button) - - - Example().run() - - :attr:`overflow_cls` is an :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - # Attributes only for the BottomAppBar class. - - icon = StringProperty() - """ - Floating button. Only for :class:`~MDBottomAppBar` class. - - :attr:`icon` is an :class:`~kivy.properties.StringProperty` - and defaults to `'android'`. - """ - - icon_color = ColorProperty() - """ - Color in (r, g, b, a) or string format action button. Only for - :class:`~MDBottomAppBar` class. - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[]`. - """ - - # MD3 Style attributes. - - anchor_title = OptionProperty(None, options=["left", "center", "right"]) - """ - Position bar title. Only used with `material_style = 'M3'` - Available options are: `'left'`, `'center'`, `'right'`. - - :attr:`anchor_title` is an :class:`~kivy.properties.OptionProperty` - and defaults to `None`. - """ - - headline_text = StringProperty() - """ - Headline text bar. - - .. versionadded:: 1.0.0 - - :attr:`headline_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - headline_text_color = ColorProperty(None) - """ - Headline text color in (r, g, b, a) or string format. - - .. versionadded:: 1.0.0 - - :attr:`headline_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - type_height = OptionProperty("small", options=["medium", "large", "small"]) - """ - Bar height type. - - .. versionadded:: 1.0.0 - - Available options are: 'medium', 'large', 'small'. - - :attr:`type_height` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'small'`. - """ - - # List of action buttons (ActionTopAppBarButton instance) that have been - # .added to the overflow - _hidden_items = [] - # See `kivymd.uix.menu.MDDropdownMenu.items` attribute. - _overflow_menu_items = [] - - def __init__(self, **kwargs): - self.action_button = MDFabBottomAppBarButton() - super().__init__(**kwargs) - self.register_event_type("on_action_button") - - if not self.icon_color: - self.icon_color = self.theme_cls.primary_color - - self.bind(specific_text_color=self.update_action_bar_text_colors) - self.theme_cls.bind( - material_style=self.update_bar_height, - primary_palette=self.update_md_bg_color, - ) - - Clock.schedule_once( - lambda x: self.on_left_action_items(0, self.left_action_items) - ) - Clock.schedule_once( - lambda x: self.on_right_action_items(0, self.right_action_items) - ) - Clock.schedule_once(lambda x: self.set_md_bg_color(0, self.md_bg_color)) - Clock.schedule_once(lambda x: self.on_type_height(0, self.type_height)) - Clock.schedule_once( - lambda x: self.update_anchor_title(self.theme_cls.material_style) - ) - Clock.schedule_once(self.update_floating_radius) - Clock.schedule_once(self.check_overflow_cls) - - def set_headline_font_style(self, interval: Union[int, float]) -> None: - if self.type_height in ("medium", "large"): - self.ids.label_headline.font_style = { - "medium": "H6", - "large": "H5", - }[self.type_height] - - def on_width(self, instance_toolbar, width: float) -> None: - """ - Called when the bar is resized (size of the application window). - """ - - if self.mode == "center": - self.action_button.x = width / 2 - self.action_button.width / 2 - else: - self.action_button.x = width - self.action_button.width * 2 - - # The user reduces the width of the window. - if ( - self.get_window_width_resizing_direction() == "left" - and self.use_overflow - and self.ids.label_title.is_shortened - ): - if not self.overflow_action_button_is_added(): - self.add_overflow_button() - self.add_action_button_to_overflow() - # The user increases the width of the window. - if ( - self.get_window_width_resizing_direction() == "right" - and self.use_overflow - and not self.ids.label_title.is_shortened - and self.overflow_cls.items - ): - self.return_action_button_to_toolbar() - - def return_action_button_to_toolbar(self) -> None: - if len(self._hidden_items): - action_button = self._hidden_items.pop() - self.ids.right_actions.add_widget(action_button, index=1) - self.update_overflow_menu_items(action_button) - if not len(self._hidden_items): - self.remove_overflow_button() - - def remove_overflow_button(self) -> None: - """Removes an overflow button to the bar.""" - - if self.overflow_action_button_is_added(): - action_overflow_button = self.ids.right_actions.children[0] - self.ids.right_actions.remove_widget(action_overflow_button) - self._overflow_menu_items = [] - - def add_overflow_button(self) -> None: - """Adds an overflow button to the bar.""" - - self.ids.right_actions.add_widget( - MDActionOverFlowButton( - theme_text_color="Custom" - if not self.opposite_colors - else "Primary", - text_color=self.specific_text_color, - opposite_colors=self.opposite_colors, - on_release=lambda x: self.overflow_cls.open(), - ) - ) - - def overflow_action_button_is_added(self) -> bool: - """ - Returns `True` if at least one action button - (:class:`~ActionTopAppBarButton') on the bar is added to the - overflow. - """ - - if ( - not self.ids.right_actions.children[0].__class__ - is MDActionOverFlowButton - ): - return False - return True - - def add_action_button_to_overflow(self): - """Adds an overflow button to the bar.""" - - if len(self.ids.right_actions.children) > 1: - button_to_be_added = self.ids.right_actions.children[1] - self._hidden_items.append(button_to_be_added) - self.ids.right_actions.remove_widget(button_to_be_added) - - self._overflow_menu_items.append( - { - "viewclass": "OverFlowMenuItem", - "icon": button_to_be_added.icon, - "text": button_to_be_added.overflow_text, - "height": dp(48), - "on_press": lambda *x: button_to_be_added.on_release(*x), - } - ) - self.overflow_cls.items = self._overflow_menu_items - self.overflow_cls.caller = self.ids.right_actions.children[0] - - def check_overflow_cls(self, interval: Union[int, float]) -> None: - """ - If the user does not set the :attr:`overflow_cls` attribute but uses - overflows, the :attr:`overflow_cls` attribute will use the default - value. - """ - - if not self.overflow_cls: - self.overflow_cls = self.get_default_overflow_cls() - - def on_type(self, instance_toolbar, type_value: str) -> None: - """Called when the value of the :attr:`type` attribute changes.""" - - if type_value == "bottom": - self.action_button.bind(center_x=self.setter("notch_center_x")) - self.action_button.bind( - on_release=lambda x: self.dispatch("on_action_button") - ) - self.action_button.x = ( - Window.width / 2 - self.action_button.width / 2 - ) - self.action_button.y = ( - (self.center[1] - self.height / 2) - + self.theme_cls.standard_increment / 2 - + self._shift - ) - self.shadow_offset = [0, 30] - self.on_mode(None, self.mode) - - def on_type_height(self, instance_toolbar, height_type_value: str) -> None: - """ - Called when the value of the :attr:`type_height` attribute changes. - """ - - if self.theme_cls.material_style == "M2": - self.height = self.theme_cls.standard_increment - else: - if self.type != "bottom": - if height_type_value == "small": - self.height = dp(64) - elif height_type_value == "medium": - self.height = dp(112) - elif height_type_value == "large": - self.height = dp(152) - else: - self.height = self.theme_cls.standard_increment - Clock.schedule_once(self.set_headline_font_style) - - def on_action_button(self, *args): - """ - Method for the button used for the :class:`~MDBottomAppBar` class. - """ - - def on_overflow_cls( - self, instance_toolbar, instance_overflow_cls: MDDropdownMenu - ) -> None: - """ - Called when the value of the :attr:`overflow_cls` attribute changes. - """ - - self.overflow_cls = instance_overflow_cls - - def on_md_bg_color(self, instance_toolbar, color_value: list) -> None: - """ - Called when the value of the :attr:`md_bg_color` attribute changes. - """ - - def on_md_bg_color(interval: Union[int, float]): - if self.type == "bottom": - self.md_bg_color = [0, 0, 0, 0] - else: - if self.set_bars_color: - set_bars_colors( - color_value, None, self.theme_cls.theme_style - ) - - Clock.schedule_once(on_md_bg_color) - - def on_left_action_items(self, instance_toolbar, items_value: list) -> None: - """ - Called when the value of the :attr:`left_action_items` attribute - changes. - """ - - def on_left_action_items(interval: Union[int, float]): - self.update_action_bar(self.ids.left_actions, items_value) - - Clock.schedule_once(on_left_action_items) - - def on_right_action_items( - self, instance_toolbar, items_value: list - ) -> None: - """ - Called when the value of the :attr:`right_action_items` attribute - changes. - """ - - def on_right_actions(interval: Union[int, float]): - self.update_action_bar(self.ids.right_actions, items_value) - - Clock.schedule_once(on_right_actions) - - def on_icon(self, instance_toolbar, icon_name: str) -> None: - """Called when the value of the :attr:`icon` attribute changes.""" - - self.action_button.icon = icon_name - - def on_icon_color(self, instance, icon_name: str) -> None: - """ - Called when the value of the :attr:`icon_color` attribute changes. - """ - - self.action_button.md_bg_color = icon_name - - def on_md_bg_bottom_color( - self, instance_toolbar, color_value: list - ) -> None: - """ - Called when the value of the :attr:`md_bg_bottom_color` attribute - changes. - """ - - set_bars_colors(None, color_value, self.theme_cls.theme_style) - - def on_anchor_title(self, instance_toolbar, anchor_value: str) -> None: - """ - Called when the value of the :attr:`anchor_title` attribute changes. - """ - - def on_anchor_title(interval: Union[int, float]): - self.ids.label_title.halign = anchor_value - - Clock.schedule_once(on_anchor_title) - - def on_mode(self, instance_toolbar, mode_value: str) -> None: - """Called when the value of the :attr:`made` attribute changes.""" - - if self.type == "top": - return - - def on_mode(interval: Union[int, float]): - def set_button_pos(*args): - self.action_button.x = x - self.action_button.y = y - self._rounded_rectangle_height / 2 - self.action_button._hard_shadow_size = (0, 0) - self.action_button._soft_shadow_size = (0, 0) - anim = Animation( - scale_value_x=1, scale_value_y=1, scale_value_z=1, d=0.05 - ) - anim.bind(on_complete=self.set_shadow) - anim.start(self.action_button) - - if mode_value == "center": - self.set_notch() - x = Window.width / 2 - self.action_button.width / 2 - y = ( - (self.center[1] - self.height / 2) - + self.theme_cls.standard_increment / 2 - + self._shift - ) - elif mode_value == "end": - self.set_notch() - x = Window.width - self.action_button.width * 2 - y = ( - (self.center[1] - self.height / 2) - + self.theme_cls.standard_increment / 2 - + self._shift - ) - self.right_action_items = [] - elif mode_value == "free-end": - self.remove_notch() - x = Window.width - self.action_button.width - dp(10) - y = self.action_button.height + self.action_button.height / 2 - elif mode_value == "free-center": - self.remove_notch() - x = Window.width / 2 - self.action_button.width / 2 - y = self.action_button.height + self.action_button.height / 2 - self.remove_shadow() - anim = Animation( - scale_value_x=0, scale_value_y=0, scale_value_z=0, d=0.1 - ) - anim.bind(on_complete=set_button_pos) - anim.start(self.action_button) - - Clock.schedule_once(on_mode) - - def set_md_bg_color(self, instance_toolbar, color_value: list) -> None: - if color_value == [1.0, 1.0, 1.0, 0.0]: - self.md_bg_color = self.theme_cls.primary_color - - def set_notch(self) -> None: - anim = Animation(d=0.1) + Animation( - notch_radius=self.action_button.width / 2 + dp(8), - d=0.1, - ) - anim.start(self) - - def set_shadow(self, *args) -> None: - self.action_button._elevation = self.action_button.elevation - - def get_default_overflow_cls(self) -> OverFlowMenu: - return OverFlowMenu(width_mult=4) - - def update_overflow_menu_items(self, action_button) -> None: - for data in self.overflow_cls.items: - if data["icon"] == action_button.icon: - self.overflow_cls.items.remove(data) - break - - def update_bar_height( - self, instance_theme_manager, material_style_value: str - ) -> None: - self.on_type_height(self, self.type_height) - self.update_anchor_title(material_style_value) - - def update_floating_radius(self, interval: Union[int, float]) -> None: - self.action_button.radius = self.action_button.width / 2 - - def update_anchor_title(self, material_style_value: str) -> str: - if material_style_value == "M2": - self.anchor_title = "left" - elif material_style_value == "M3" and self.type != "bottom": - if not self.anchor_title: - self.anchor_title = "center" - elif material_style_value == "M3" and self.type == "bottom": - self.anchor_title = "left" - return self.anchor_title - - def update_action_bar( - self, instance_box_layout, action_bar_items: list - ) -> None: - instance_box_layout.clear_widgets() - new_width = 0 - - for item in action_bar_items: - new_width += dp(48) - if len(item) == 1: - item.append(lambda x: None) - if len(item) > 1 and not item[1]: - item[1] = lambda x: None - if len(item) == 2: - if isinstance(item[1], str) or isinstance(item[1], tuple): - item.insert(1, lambda x: None) - else: - item.append("") - if len(item) == 3: - if isinstance(item[2], tuple): - item.insert(2, "") - - instance_box_layout.add_widget( - ActionTopAppBarButton( - icon=item[0], - on_release=item[1], - tooltip_text=item[2], - overflow_text=item[3] - if (len(item) == 4 and isinstance(item[3], str)) - else "", - theme_text_color="Custom" - if not self.opposite_colors - else "Primary", - text_color=self.specific_text_color - if not (len(item) == 4 and isinstance(item[3], tuple)) - else item[3], - opposite_colors=self.opposite_colors, - ) - ) - - instance_box_layout.width = new_width - - def update_md_bg_color(self, *args) -> None: - self.md_bg_color = self.theme_cls._get_primary_color() - - def update_action_bar_text_colors(self, *args) -> None: - for child in self.ids.left_actions.children: - child.text_color = self.specific_text_color - for child in self.ids.right_actions.children: - child.text_color = self.specific_text_color - - def remove_notch(self) -> None: - anim = Animation(d=0.1) + Animation(notch_radius=0, d=0.1) - anim.start(self) - - def remove_shadow(self) -> None: - self.action_button._elevation = 0 - - def _update_specific_text_color(self, instance, value): - if self.specific_text_color in ( - [0.0, 0.0, 0.0, 0.87], - [0.0, 0.0, 0.0, 1.0], - [1.0, 1.0, 1.0, 1.0], - ): - self.specific_text_color = text_colors[ - self.theme_cls.primary_palette - ][self.theme_cls.primary_hue] - - -class MDBottomAppBar( - DeclarativeBehavior, - ThemableBehavior, - SpecificBackgroundColorBehavior, - CommonElevationBehavior, - FloatLayout, -): - """ - Bottom app bar class. - - For more information, see in the - :class:`~kivymd.uix.behaviors.DeclarativeBehavior` and - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivymd.uix.behaviors.SpecificBackgroundColorBehavior` and - :class:`~kivymd.uix.behaviors.CommonElevationBehavior` and - :class:`~kivy.uix.floatlayout.FloatLayout` - classes documentation. - - :Events: - `on_show_bar` - The method is called when the :class:`~MDBottomAppBar` panel - is shown. - `on_hide_bar` - The method is called when the :class:`~MDBottomAppBar` panel - is hidden. - """ - - md_bg_color = ColorProperty([0, 0, 0, 0]) - """ - Color bar in (r, g, b, a) or string format. - - :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `[0, 0, 0, 0]`. - """ - - icon_color = ColorProperty(None) - """ - Color bar in (r, g, b, a) or string format. - - .. versionadded:: 1.2.0 - - :attr:`icon_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - action_items = ListProperty() - """ - The icons on the left bar. - - .. versionadded:: 1.2.0 - - :attr:`action_items` is an :class:`~kivy.properties.ListProperty` - and defaults to `[]`. - """ - - animation = BooleanProperty(True) - """ - # TODO: add description. - # FIXME: changing the value does not affect anything. - - .. versionadded:: 1.2.0 - - :attr:`animation` is an :class:`~kivy.properties.BooleanProperty` - and defaults to `True`. - """ - - show_transition = StringProperty("linear") - """ - Type of button display transition. - - .. versionadded:: 1.2.0 - - :attr:`show_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'linear'`. - """ - - hide_transition = StringProperty("in_back") - """ - Type of button hidden transition. - - .. versionadded:: 1.2.0 - - :attr:`hide_transition` is a :class:`~kivy.properties.StringProperty` - and defaults to `'in_back'`. - """ - - hide_duration = NumericProperty(0.4) - """ - Duration of button hidden transition. - - .. versionadded:: 1.2.0 - - :attr:`hide_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - show_duration = NumericProperty(0.2) - """ - Duration of button display transition. - - .. versionadded:: 1.2.0 - - :attr:`show_duration` is a :class:`~kivy.properties.NumericProperty` - and defaults to `0.2`. - """ - - scroll_cls = ObjectProperty() - """ - Widget inherited from the :class:`~kivy.uix.scrollview.ScrollView` class. - The value must be set if the :attr:`allow_hidden` parameter is `True`. - - .. versionadded:: 1.2.0 - - :attr:`scroll_cls` is a :class:`~kivy.properties.ObjectProperty` - and defaults to `None`. - """ - - allow_hidden = BooleanProperty(False) - """ - Allows or disables hiding the panel when scrolling content. - If the value is `True`, the :attr:`scroll_cls` parameter must be specified. - - .. versionadded:: 1.2.0 - - :attr:`allow_hidden` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - bar_is_hidden = BooleanProperty(False) - """ - Is the panel currently hidden. - - .. versionadded:: 1.2.0 - - :attr:`bar_is_hidden` is a :class:`~kivy.properties.BooleanProperty` - and defaults to `False`. - """ - - _padding = dp(16) - _x = -dp(48) - _scroll_cls_y = 0 - _cache = [] - _current_data = [] - _wait_removed = False - _animated_hidden = True - _animated_show = True - _fab_bottom_app_bar_button = None - _action_overflow_button = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.size_hint_y = None - if self.theme_cls.material_style == "M3": - self.register_event_type("on_show_bar") - self.register_event_type("on_hide_bar") - - self.height = dp(80) - Clock.schedule_once(self.set_bg_color) - - def button_centering_animation( - self, - button: MDActionOverFlowButton - | MDActionBottomAppBarButton - | MDFabBottomAppBarButton, - ) -> None: - """ - Animation of centering buttons for - :class:`~MDActionOverFlowButton`, - :class:`~MDActionBottomAppBarButton` and - :class:`~MDFabBottomAppBarButton` classes. - """ - - if self.animation: - Animation( - y=self.height / 2 - dp(48) / 2, - opacity=1, - d=self.show_duration, - t=self.show_transition, - ).start(button) - - def check_scroll_direction(self, scroll_cls, y: float) -> None: - """ - Checks the scrolling direction. - Depending on the scrolling direction, hides or shows the - :class:`~MDBottomAppBar` panel. - """ - - if round(y, 1) < self._scroll_cls_y and not self.bar_is_hidden: - self.hide_bar() - if round(y, 1) > self._scroll_cls_y and self.bar_is_hidden: - self.show_bar() - - self._scroll_cls_y = round(y, 1) - - def show_bar(self) -> None: - """Show :class:`~MDBottomAppBar` panel.""" - - def on_complete(*args): - self.dispatch("on_show_bar") - - def on_progress(animation, instance, progress): - if progress > 0.5 and self._animated_show: - self._animated_show = False - for i, widget in enumerate(self.children): - if isinstance(widget, MDActionBottomAppBarButton): - anim_icon = Animation( - y=self.height / 2 - dp(48) / 2, - d=self.show_duration, - t=self.show_transition, - ) - Clock.schedule_once( - lambda x, y=widget: anim_icon.start(y), - i / 10, - ) - if self._fab_bottom_app_bar_button: - Animation( - y=self._fab_bottom_app_bar_button.y + dp(4), - d=self.show_duration, - t=self.show_transition, - ).start(self._fab_bottom_app_bar_button) - - self.bar_is_hidden = False - self._animated_show = True - anim = Animation( - y=0, - d=self.show_duration, - t=self.show_transition, - ) - anim.bind(on_progress=on_progress, on_complete=on_complete) - anim.start(self) - - def hide_bar(self) -> None: - """Hide :class:`~MDBottomAppBar` panel.""" - - def on_complete(*args): - self.dispatch("on_hide_bar") - - def on_progress(animation, instance, progress): - if ( - progress > 0.5 - and self._animated_hidden - and widget_icon == instance.icon - ): - self._animated_hidden = False - anim_bar = Animation( - y=-self.height, - d=self.hide_duration, - # t=self.hide_transition, - ) - anim_bar.bind(on_complete=on_complete) - anim_bar.start(self) - - if self._fab_bottom_app_bar_button: - Animation( - y=self._fab_bottom_app_bar_button.y - dp(4), - d=self.hide_duration, - t=self.hide_transition, - ).start(self._fab_bottom_app_bar_button) - - self.bar_is_hidden = True - self._animated_hidden = True - len_children = len(self.children) - widget_icon = "" - - for i, widget in enumerate(self.children): - if isinstance(widget, MDActionBottomAppBarButton): - anim = Animation( - y=-widget.height, - d=self.hide_duration, - t=self.hide_transition, - ) - if i + 2 == len_children: - widget_icon = widget.icon - anim.bind(on_progress=on_progress) - Clock.schedule_once( - lambda x, y=widget: anim.start(y), - i / 10, - ) - - def on_show_bar(self, *args) -> None: - """ - The method is called when the :class:`~MDBottomAppBar` panel - is shown. - """ - - def on_hide_bar(self, *args) -> None: - """ - The method is called when the :class:`~MDBottomAppBar` panel - is hidden. - """ - - def on_scroll_cls(self, instance, scroll_cls) -> None: - """ - Called when the value of the :attr:`scroll_cls` attribute changes. - """ - - def on_scroll_cls(*args): - if not self.allow_hidden: - Logger.warning( - "KivyMD: " - "In order for the bottom bar to be automatically hidden " - "in addition to the `scroll_cls` parameter, set the value " - "of the `allow_hidden` parameter to `True`" - ) - - if issubclass(scroll_cls.__class__, ScrollView): - if self.allow_hidden: - scroll_cls.bind(scroll_y=self.check_scroll_direction) - else: - raise TypeError( - f"The `scroll_cls` parameter must be an object inherited from " - f"the {ScrollView} class" - ) - - if self.theme_cls.material_style == "M3": - Clock.schedule_once(on_scroll_cls) - - def on_size(self, *args) -> None: - """Called when the root screen is resized.""" - - if ( - self._fab_bottom_app_bar_button - and self.theme_cls.material_style == "M3" - ): - self._fab_bottom_app_bar_button.x = Window.width - (dp(56) + dp(16)) - - def on_action_items(self, instance, value: list) -> None: - """ - Called when the value of the :attr:`action_items` attribute changes. - """ - - if self.theme_cls.material_style == "M2": - return - - def wait_removed(*args): - if len(self.children) == 1 or not self.children: - Clock.unschedule(wait_removed) - self._wait_removed = False - self._x = -dp(48) - asynckivy.start(add_widget()) - - async def add_widget(): - for button in value: - await asynckivy.sleep(0) - self.add_widget(button) - - if self._cache: - self._cache.append(value) - - for data in self._cache: - if value[0] in data: - for i, widget in enumerate(self.children): - if not self._wait_removed: - Clock.schedule_interval(wait_removed, 0) - self._wait_removed = True - if isinstance(widget, MDActionBottomAppBarButton): - anim = Animation( - y=-widget.height, - d=self.hide_duration, - t=self.hide_transition, - ) - anim.bind( - on_complete=lambda x, y=widget: self.remove_widget( - y - ) - ) - Clock.schedule_once( - lambda x, y=widget: anim.start(y), - i / 10, - ) - else: - self._cache.append(value) - self._current_data = value - asynckivy.start(add_widget()) - - def set_fab_opacity(self, *ars) -> None: - """ - Sets the transparency value of the:class:`~MDFabBottomAppBarButton` - button. - """ - - self._fab_bottom_app_bar_button.ids.lbl_ic.opacity = 1 - - def set_fab_icon(self, instance, value) -> None: - """ - Animates the size of the :class:`~MDFabBottomAppBarButton` button. - """ - - self._fab_bottom_app_bar_button.ids.lbl_ic.opacity = 0 - anim = Animation( - scale_value_x=0, - scale_value_y=0, - opacity=0, - d=self.hide_duration, - t=self.hide_transition, - ) + Animation( - scale_value_x=1, - scale_value_y=1, - opacity=1, - d=self.show_duration, - t=self.show_transition, - ) - anim.bind(on_complete=self.set_fab_opacity) - anim.start(instance) - - def set_bg_color(self, *args) -> None: - """ - Sets the background color for the :class:`~MDBottomAppBar` class. - """ - - if self.md_bg_color == [0, 0, 0, 0]: - self.md_bg_color = self.theme_cls.primary_color - - def set_icon_color( - self, widget: MDActionOverFlowButton | MDActionBottomAppBarButton - ) -> None: - """ - Sets the icon color for the :class:`~MDActionOverFlowButton` and - :class:`~MDActionBottomAppBarButton` classes. - """ - - if self.icon_color: - widget.theme_icon_color = "Custom" - widget.icon_color = self.icon_color - - def add_widget(self, widget, index=0, canvas=None): - # For M2 style. - if ( - isinstance(widget, MDTopAppBar) - and self.theme_cls.material_style == "M2" - ): - super().add_widget(widget) - widget.elevation = 0 - return super().add_widget(widget.action_button) - # For M3 style. - if self.theme_cls.material_style == "M3": - if isinstance(widget, MDActionBottomAppBarButton): - self._x += widget.width - widget.pos = ( - self._x + self._padding, - -dp(48) if self.animation else self.height / 2 - dp(48) / 2, - ) - widget.opacity = int(not self.animation) - self.set_icon_color(widget) - super().add_widget(widget) - self.button_centering_animation(widget) - elif isinstance(widget, MDFabBottomAppBarButton): - widget.bind(icon=self.set_fab_icon) - self._fab_bottom_app_bar_button = widget - Clock.schedule_once(self.set_fab_opacity) - widget.scale_value_x = int(not self.animation) - widget.scale_value_y = int(not self.animation) - widget.pos = ( - Window.width - (dp(56) + self._padding), - self.height / 2 - dp(56) / 2, - ) - super().add_widget(widget) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__init__.py index 7ee554f..71623b2 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__init__.py @@ -1 +1,8 @@ -from .tooltip import MDTooltip, MDTooltipViewClass # NOQA F401 +from .tooltip import ( + MDTooltip, + MDTooltipPlain, + MDTooltipRich, + MDTooltipRichSubhead, + MDTooltipRichActionButton, + MDTooltipRichSupportingText, +) # NOQA F401 diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/__init__.cpython-311.pyc index 97e25bf..53d996f 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/tooltip.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/tooltip.cpython-311.pyc index 4d0e9a5..9bee820 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/tooltip.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/__pycache__/tooltip.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.kv b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.kv index 266bed5..13a5f43 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.kv +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.kv @@ -1,38 +1,78 @@ - - size_hint: None, None - width: self.minimum_width - height: self.minimum_height + root.padding[1] + + adaptive_size: True opacity: 0 + font_style: "Body" + role: "small" + padding: "8dp", "4dp", "8dp", "4dp" + radius: [dp(4), ] + scale_value_x: 0 + scale_value_y: 0 + text_color: + self.theme_cls.inverseOnSurfaceColor \ + if self.theme_text_color == "Primary" else \ + self.text_color canvas.before: - PushMatrix Color: rgba: - root.theme_cls.opposite_bg_dark if not root.tooltip_bg_color \ - else root.tooltip_bg_color + self.theme_cls.inverseSurfaceColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color RoundedRectangle: - pos: self.pos size: self.size - radius: root.tooltip_radius - Scale: - origin: self.center - x: root._scale_x - y: root._scale_y - canvas.after: - PopMatrix + pos: self.pos + radius: self.radius - MDLabel: - id: label_tooltip - text: root.tooltip_text - size_hint: None, None - -text_size: None, None - size: self.texture_size - bold: True - theme_text_color: "Custom" - font_style: root.tooltip_font_style - markup: True - pos_hint: {"center_y": .5} - text_color: - ([0, 0, 0, 1] if not root.tooltip_text_color else root.tooltip_text_color) \ - if root.theme_cls.theme_style == "Dark" else \ - ([1, 1, 1, 1] if not root.tooltip_text_color else root.tooltip_text_color) + + + orientation: "vertical" + scale_value_x: 0 + scale_value_y: 0 + opacity: 0 + radius: [dp(12), ] + size_hint: None, None + size: self.minimum_size + padding: "16dp", "12dp", "16dp", "8dp" + spacing: "4dp" + elevation_level: 2 + elevation: self.elevation_levels[self.elevation_level] + md_bg_color: + self.theme_cls.surfaceContainerColor \ + if self.theme_bg_color == "Primary" else \ + self.md_bg_color + shadow_softness: + 2 \ + if self.theme_shadow_softness == "Primary" else \ + self.shadow_softness + shadow_offset: + (0, -1) \ + if self.theme_shadow_offset == "Primary" else \ + self.shadow_offset + shadow_radius: [value - 2 for value in self.radius] + + + bold: True + adaptive_size: True + font_style: "Title" + role: "small" + text_color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_text_color == "Primary" else \ + self.text_color + + + + style: "text" + _text_left_pad: 0 + _text_right_pad: 0 + ripple_effect: False + + + + adaptive_size: True + font_style: "Body" + role: "medium" + text_color: + self.theme_cls.onSurfaceVariantColor \ + if self.theme_text_color == "Primary" else \ + self.text_color diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.py index ba6b5da..1d70fc8 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/tooltip/tooltip.py @@ -4,70 +4,179 @@ Components/Tooltip .. seealso:: - `Material Design spec, Tooltips `_ + `Material Design spec, Tooltips `_ -.. rubric:: Tooltips display informative text when users hover over, focus on, - or tap an element. +.. rubric:: Tooltips display brief labels or messages. -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip.png +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip-m3-preview.png :align: center -To use the :class:`~MDTooltip` class, you must create a new class inherited -from the :class:`~MDTooltip` class: +- Use tooltips to add additional context to a button or other UI element +- Two types: plain and rich +- Use plain tooltips to describe elements or actions of icon buttons +- Use rich tooltips to provide more details, like describing the value of a feature +- Rich tooltips can include an optional title, link, and buttons -In Kv-language: +**KivyMD provides two types of tooltip:** -.. code-block:: kv +1. Plain tooltip +2. Rich tooltip - +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip-m3-type.png + :align: center -In Python code: - -.. code-block:: python - - class TooltipMDIconButton(MDIconButton, MDTooltip): - pass - -.. Warning:: :class:`~MDTooltip` only works correctly with button and label classes. +Usage of tooltip plain +---------------------- .. code-block:: python from kivy.lang import Builder + from kivy.properties import StringProperty + from kivymd.uix.button import MDButton + from kivymd.uix.tooltip import MDTooltip from kivymd.app import MDApp KV = ''' - + + + MDTooltipPlain: + text: + "Grant value is calculated using the closing stock price \\\\n" \\ + "from the day before the grant date. Amounts do not \\\\n" \\ + "reflect tax witholdings." + + + + + MDButtonText: + text: root.text MDScreen: + md_bg_color: self.theme_cls.backgroundColor TooltipMDIconButton: - icon: "language-python" - tooltip_text: self.icon + text: "Tooltip button" pos_hint: {"center_x": .5, "center_y": .5} ''' - class Test(MDApp): + class YourTooltipClass(MDTooltip): + '''Implements your tooltip base class.''' + + + class TooltipMDIconButton(YourTooltipClass, MDButton): + '''Implements a button with tooltip behavior.''' + + text = StringProperty() + + + class Example(MDApp): def build(self): + self.theme_cls.primary_palette = "Olive" return Builder.load_string(KV) - Test().run() + Example().run() -.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip.gif +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip-m3-plain-usage.gif :align: center -.. Note:: The behavior of tooltips on desktop and mobile devices is different. - For more detailed information, - `click here `_. +The anatomy of a plain tooltip +------------------------------ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip-m3-plain-anatomy.png + :align: center + +Usage of tooltip rich +--------------------- + +.. code-block:: python + + from kivy.lang import Builder + from kivy.properties import StringProperty + + from kivymd.uix.button import MDButton + from kivymd.uix.tooltip import MDTooltip + from kivymd.app import MDApp + + KV = ''' + + + MDTooltipRich: + id: tooltip + auto_dismiss: False + + MDTooltipRichSubhead: + text: "Add others" + + MDTooltipRichSupportingText: + text: + "Grant value is calculated using the closing stock price \\\\n" \\ + "from the day before the grant date. Amounts do not \\\\n" \\ + "reflect tax witholdings." + + MDTooltipRichActionButton: + on_press: tooltip.dismiss() + + MDButtonText: + text: "Learn more" + + + + + MDButtonText: + text: root.text + + + MDScreen: + md_bg_color: self.theme_cls.backgroundColor + + TooltipMDIconButton: + text: "Tooltip button" + pos_hint: {"center_x": .5, "center_y": .5} + ''' + + + class YourTooltipClass(MDTooltip): + '''Implements your tooltip base class.''' + + + class TooltipMDIconButton(YourTooltipClass, MDButton): + '''Implements a button with tooltip behavior.''' + + text = StringProperty() + + + class Example(MDApp): + def build(self): + self.theme_cls.primary_palette = "Olive" + return Builder.load_string(KV) + + + Example().run() + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip-m3-rich-usage.gif + :align: center + +The anatomy of a plain tooltip +------------------------------ + +.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tooltip-m3-rich-anatomy.png + :align: center """ -__all__ = ("MDTooltip", "MDTooltipViewClass") +__all__ = ( + "MDTooltip", + "MDTooltipPlain", + "MDTooltipRich", + "MDTooltipRichActionButton", + "MDTooltipRichSubhead", + "MDTooltipRichSupportingText", +) import os -from typing import Union from kivy.animation import Animation from kivy.clock import Clock @@ -76,19 +185,24 @@ from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( BoundedNumericProperty, - ColorProperty, - ListProperty, NumericProperty, - OptionProperty, - StringProperty, + BooleanProperty, ) from kivy.uix.boxlayout import BoxLayout +from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior +from kivymd.uix.button import MDButton +from kivymd.uix.label import MDLabel from kivymd import uix_path -from kivymd.font_definitions import theme_font_styles from kivymd.material_resources import DEVICE_TYPE from kivymd.theming import ThemableBehavior -from kivymd.uix.behaviors import HoverBehavior, TouchBehavior +from kivymd.uix.behaviors import ( + TouchBehavior, + ScaleBehavior, + DeclarativeBehavior, + BackgroundColorBehavior, + CommonElevationBehavior, +) with open( os.path.join(uix_path, "tooltip", "tooltip.kv"), encoding="utf-8" @@ -96,74 +210,34 @@ with open( Builder.load_string(kv_file.read()) -class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): +class MDTooltip(TouchBehavior): """ Tooltip class. For more information, see in the - :class:`~kivymd.theming.ThemableBehavior and - :class:`~kivymd.uix.behaviors.HoverBehavior` and - :class:`~kivymd.uix.behaviors.TouchBehavior` - classes documentation. - """ + :class:`~kivymd.uix.behaviors.touch_behavior.TouchBehavior` + class documentation. - tooltip_bg_color = ColorProperty(None) - """ - Tooltip background color in (r, g, b, a) or string format - - :attr:`tooltip_bg_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tooltip_text_color = ColorProperty(None) - """ - Tooltip text color in (r, g, b, a) or string format - - :attr:`tooltip_text_color` is an :class:`~kivy.properties.ColorProperty` - and defaults to `None`. - """ - - tooltip_text = StringProperty() - """ - Tooltip text. - - :attr:`tooltip_text` is an :class:`~kivy.properties.StringProperty` - and defaults to `''`. - """ - - tooltip_font_style = OptionProperty("Caption", options=theme_font_styles) - """ - Tooltip font style. Available options are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, - `'H5'`, `'H6'`, `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, - `'Button'`, `'Caption'`, `'Overline'`, `'Icon'`. - - :attr:`tooltip_font_style` is an :class:`~kivy.properties.OptionProperty` - and defaults to `'Caption'`. - """ - - tooltip_radius = ListProperty( - [ - dp(7), - ] - ) - """ - Corner radius values. - - :attr:`radius` is an :class:`~kivy.properties.ListProperty` - and defaults to `[dp(7),]`. + :Events: + `on_open`: + Fired when the tooltip opens. + `on_dismiss`: + Fired when the tooltip is closed. """ tooltip_display_delay = BoundedNumericProperty(0, min=0, max=4) """ - Tooltip dsiplay delay. + Tooltip display delay. + + .. note:: This property only works on desktop. :attr:`tooltip_display_delay` is an :class:`~kivy.properties.BoundedNumericProperty` - and defaults to `0`, min of `0` & max of `4`. This property only works on desktop. + and defaults to `0`, min of `0` & max of `4`. """ shift_y = NumericProperty() """ - Y-offset of tooltip text. + Y-offset of tooltip to the top. :attr:`shift_y` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. @@ -171,7 +245,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): shift_right = NumericProperty() """ - Shifting the tooltip text to the right. + Shifting the tooltip to the right. .. versionadded:: 1.0.0 @@ -181,7 +255,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): shift_left = NumericProperty() """ - Shifting the tooltip text to the left. + Shifting the tooltip to the left. .. versionadded:: 1.0.0 @@ -193,7 +267,7 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): def __init__(self, **kwargs): super().__init__(**kwargs) - self.register_event_type("on_show") + self.register_event_type("on_open") self.register_event_type("on_dismiss") def delete_clock(self, widget, touch, *args): @@ -204,12 +278,26 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): pass self.on_leave() - def adjust_tooltip_position(self, x: float, y: float) -> tuple: + def adjust_tooltip_position(self) -> tuple: """ - Returns the coordinates of the tooltip that fit into the borders of the - screen. + Returns the coordinates of the tooltip that fit into the borders + of the screen. """ + pos = self.to_window(self.center_x, self.center_y) + if not self.shift_right and not self.shift_left: + x = pos[0] - (self._tooltip.width / 2) + else: + if self.shift_right: + x = pos[0] - (self._tooltip.width / 2) + self.shift_right + if self.shift_left: + x = pos[0] - (self._tooltip.width / 2) - self.shift_left + + if not self.shift_y: + y = pos[1] - (self._tooltip.height + self.height) + else: + y = pos[1] - self._tooltip.height / 2 - self.height + self.shift_y + # If the position of the tooltip is outside the right border # of the screen. if x + self._tooltip.width > Window.width: @@ -228,27 +316,14 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): y = Window.height - (self._tooltip.height + dp(10)) return x, y - def display_tooltip(self, interval: Union[int, float]) -> None: + def display_tooltip(self, *args) -> None: + """Adds a tooltip widget to the screen and animates its display.""" + if not self._tooltip or self._tooltip.parent: return Window.add_widget(self._tooltip) - pos = self.to_window(self.center_x, self.center_y) - - if not self.shift_right and not self.shift_left: - x = pos[0] - (self._tooltip.width / 2) - else: - if self.shift_right: - x = pos[0] - (self._tooltip.width / 2) + self.shift_right - if self.shift_left: - x = pos[0] - (self._tooltip.width / 2) - self.shift_left - - if not self.shift_y: - y = pos[1] - self._tooltip.height / 2 - self.height / 2 - dp(20) - else: - y = pos[1] - self._tooltip.height / 2 - self.height + self.shift_y - - x, y = self.adjust_tooltip_position(x, y) + x, y = self.adjust_tooltip_position() self._tooltip.pos = (x, y) if DEVICE_TYPE == "desktop": @@ -258,27 +333,31 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): else: Clock.schedule_once(self.animation_tooltip_show, 0) - def animation_tooltip_show(self, interval: Union[int, float]) -> None: + def animation_tooltip_show(self, *args) -> None: """Animation of opening tooltip on the screen.""" if self._tooltip: + self._tooltip.shadow_color = self._tooltip.theme_cls.shadowColor ( - Animation(_scale_x=1, _scale_y=1, d=0.1) + Animation(scale_value_x=1, scale_value_y=1, d=0.2) + Animation(opacity=1, d=0.2) ).start(self._tooltip) - self.dispatch("on_show") + self.dispatch("on_open") - def animation_tooltip_dismiss(self, interval: Union[int, float]) -> None: + def animation_tooltip_dismiss(self, *args) -> None: """ - .. versionadded:: 1.0.0 - Animation of closing tooltip on the screen. + + .. versionadded:: 1.0.0 """ if self._tooltip: - anim = Animation(_scale_x=0, _scale_y=0, d=0.1) + Animation( - opacity=0, d=0.2 + self._tooltip.shadow_color = ( + self._tooltip.theme_cls.transparentColor ) + anim = Animation( + scale_value_x=0, scale_value_y=0, d=0.2 + ) + Animation(opacity=0, d=0.2) anim.bind(on_complete=self._on_dismiss_anim_complete) anim.start(self._tooltip) @@ -287,101 +366,170 @@ class MDTooltip(ThemableBehavior, HoverBehavior, TouchBehavior): Window.remove_widget(self._tooltip) + def add_widget(self, widget, *args, **kwargs): + """Add a new widget as a child of this widget.""" + + if isinstance(widget, (MDTooltipPlain, MDTooltipRich)): + self._tooltip = widget + widget._tooltip = self + else: + return super().add_widget(widget) + def on_long_touch(self, touch, *args) -> None: if DEVICE_TYPE != "desktop": - self.on_enter() + Clock.schedule_once(self.display_tooltip, -1) + Clock.schedule_once( + self.animation_tooltip_show, self.tooltip_display_delay + ) def on_enter(self, *args) -> None: - """ - See - :attr:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior.on_enter` - method in :class:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior` - class. - """ + """Fired when mouse enter the bbox of the widget.""" - if self.tooltip_text: + super().on_enter() + + if DEVICE_TYPE == "desktop": if self._tooltip: self.remove_tooltip() - self._tooltip = MDTooltipViewClass( - tooltip_bg_color=self.tooltip_bg_color, - tooltip_text_color=self.tooltip_text_color, - tooltip_text=self.tooltip_text, - tooltip_font_style=self.tooltip_font_style, - tooltip_radius=self.tooltip_radius, - ) - Clock.schedule_once(self.display_tooltip, -1) + Clock.schedule_once(self.display_tooltip, 0.2) - def on_leave(self) -> None: - """ - See - :attr:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior.on_leave` - method in :class:`~kivymd.uix.behaviors.hover_behavior.HoverBehavior` - class. - """ + def on_leave(self, *args) -> None: + """Fired when the mouse goes outside the widget border.""" - if self._tooltip: + super().on_leave() + + if self._tooltip and ( + not isinstance(self._tooltip, MDTooltipRich) + or self._tooltip.auto_dismiss + ): Clock.schedule_once(self.animation_tooltip_dismiss) - def on_show(self) -> None: - """Default display event handler.""" + def on_open(self) -> None: + """ + Default display event handler. + + .. versionchanged:: 2.0.0 Rename from `on_show` to `on_open`. + """ def on_dismiss(self) -> None: """ - .. versionadded:: 1.0.0 - Default dismiss event handler. + + .. versionadded:: 1.0.0 """ def _on_dismiss_anim_complete(self, *args): self.dispatch("on_dismiss") self.remove_tooltip() - self._tooltip = None + # self._tooltip = None + + def _on_release(self, *args): + ... -class MDTooltipViewClass(ThemableBehavior, BoxLayout): +class MDTooltipPlain(MDLabel, ScaleBehavior): """ - Tooltip view class. + Tooltip plain class. + + .. versionadded:: 2.0.0 For more information, see in the - :class:`~kivymd.theming.ThemableBehavior` and - :class:`~kivy.uix.boxlayout.BoxLayout` + :class:`~kivymd.uix.label.label.MDLabel` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` classes documentation. """ - tooltip_bg_color = ColorProperty(None) + +class MDTooltipRichSupportingText(MDLabel): """ - See :attr:`~MDTooltip.tooltip_bg_color`. + Implements supporting text for the :class:`~MDTooltipRich` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. """ - tooltip_text_color = ColorProperty(None) + +class MDTooltipRichSubhead(MDLabel): """ - See :attr:`~MDTooltip.tooltip_text_color`. + Implements subhead text for the :class:`~MDTooltipRich` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.label.label.MDLabel` class documentation. """ - tooltip_text = StringProperty() + +class MDTooltipRichActionButton(MDButton): """ - See :attr:`~MDTooltip.tooltip_text`. + Implements action button for the :class:`~MDTooltipRich` class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.button.button.MDButton` class documentation. """ - tooltip_font_style = OptionProperty("Caption", options=theme_font_styles) + # Override methods. + # Their functionality is not needed in this class. + + def _set_state_layer_color(self) -> None: + ... + + def on_enter(self) -> None: + ... + + def on_leave(self) -> None: + ... + + +class MDTooltipRich( + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + CommonElevationBehavior, + ScaleBehavior, + StateLayerBehavior, + BoxLayout, +): """ - See :attr:`~MDTooltip.tooltip_font_style`. + Tooltip rich class. + + .. versionadded:: 2.0.0 + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.behaviors.elevation.CommonElevationBehavior` and + :class:`~kivymd.uix.behaviors.scale_behavior.ScaleBehavior` and + :class:`~kivymd.uix.behaviors.state_layer_behavior.StateLayerBehavior` and + :class:`~kivy.uix.boxlayout.BoxLayout` and + classes documentation. """ - tooltip_radius = ListProperty() + auto_dismiss = BooleanProperty(True) """ - See :attr:`~MDTooltip.tooltip_radius`. + This property determines if the view is automatically dismissed when + the cursor goes outside of the tooltip body. + + :attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty` and + defaults to True. """ - _scale_x = NumericProperty(0) - _scale_y = NumericProperty(0) + _tooltip = None - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.padding = [ - dp(8) if DEVICE_TYPE == "desktop" else dp(16), - dp(4), - dp(8) if DEVICE_TYPE == "desktop" else dp(16), - dp(4), - ] + def on_leave(self) -> None: + """Fired when the mouse goes outside the widget border.""" + + super().on_leave() + + if self._tooltip: + Clock.schedule_once(self._tooltip.animation_tooltip_dismiss) + + def dismiss(self) -> None: + """Hides the tooltip.""" + + self.on_leave() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__init__.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__init__.py index f2c0a58..6302f35 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__init__.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__init__.py @@ -2,4 +2,5 @@ from .transition import ( # NOQA F401 MDFadeSlideTransition, MDSlideTransition, MDSwapTransition, + MDSharedAxisTransition, ) diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/__init__.cpython-311.pyc index 29198eb..c4ac86b 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/transition.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/transition.cpython-311.pyc index 90e135a..a15eea4 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/transition.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/__pycache__/transition.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/transition.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/transition.py index e905ea6..c1f5f98 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/transition.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/transition/transition.py @@ -33,20 +33,30 @@ __all__ = ( "MDSlideTransition", "MDSwapTransition", "MDTransitionBase", + "MDSharedAxisTransition", ) from kivy import Logger from kivy.animation import Animation, AnimationTransition -from kivy.properties import DictProperty +from kivy.properties import ( + DictProperty, + OptionProperty, + NumericProperty, + BooleanProperty, +) from kivy.uix.screenmanager import ( ScreenManagerException, SlideTransition, SwapTransition, TransitionBase, ) +from kivy.graphics import PopMatrix, PushMatrix, Scale +from kivy.animation import Animation, AnimationTransition +from kivy.metrics import dp from kivymd.uix.hero import MDHeroFrom, MDHeroTo from kivymd.uix.screenmanager import MDScreenManager +from kivymd.animation import MDAnimationTransition class MDTransitionBase(TransitionBase): @@ -298,3 +308,190 @@ class MDFadeSlideTransition(MDSlideTransition): self.manager.y - self.manager.height * progression ) self.screen_out.opacity = 1 - progression + + +class MDSharedAxisTransition(MDTransitionBase): + """ + Android default screen transition. + + .. versionadded:: 2.0.0 + """ + + transition_axis = OptionProperty("x", options=["x", "y", "z"]) + """ + Axis of the transition. Available values "x", "y", and "z". + + .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/transition_axis.gif + :align: center + + :attr:`transition_axis` is an :class:`~kivy.properties.OptionProperty` + and defaults to `"x"`. + """ + + duration = NumericProperty(0.15) + """ + Duration in seconds of the transition. Android recommends these intervals: + + .. list-table:: Android transition values (in seconds) + :align: left + :header-rows: 1 + + * - Name + - value + * - small_1 + - 0.075 + * - small_2 + - 0.15 + * - medium_1 + - 0.2 + * - medium_2 + - 0.25 + * - large_1 + - 0.3 + * - large_2 + - 0.35 + + :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and + defaults to 0.15 (= 150ms). + """ + + switch_animation = OptionProperty( + "easing_decelerated", + options=[ + "easing_standard", + "easing_decelerated", + "easing_accelerated", + "easing_linear", + ], + ) + """ + Custom material design animation transition. + + :attr:`switch_animation` is a :class:`~kivy.properties.OptionProperty` and + defaults to `"easing_decelerated"`. + """ + + slide_distance = NumericProperty(dp(15)) + """ + Distance to which it slides left, right, bottom or up depending on axis. + + :attr:`slide_distance` is a :class:`~kivy.properties.NumericProperty` and + defaults to `dp(15)`. + """ + + opposite = BooleanProperty(False) + """ + Decides Transition direction. + + :attr:`opposite` is a :class:`~kivy.properties.BooleanProperty` and + defaults to `False`. + """ + + _s_map = {} # scale instruction map + _slide_diff = 0 + + def start(self, manager): + # Transition internal working (for developer only): + # x: + # First half: screen_out opacity 1 -> 0, pos_x: 0 -> - slide distance + # Second half: screen_in opacity 0 -> 1, pos_x: slide distance -> 0 + # y: + # First half: screen_out opacity 1 -> 0, pos_y: 0 -> - slide distance + # Second half: screen_in opacity 0 -> 1, pos_y: slide distance -> 0 + # z: + # First half: screen_out opacity 1 -> 0, scale: 1 -> relative subtracted area + # Second half: screen_in opacity 0 -> 1, scale: relative subtracted area -> 1 + + # Save hash of the objects + self.ih = hash(self.screen_in) + self.oh = hash(self.screen_out) + + # Init pos + self.screen_in.pos = manager.pos + self.screen_out.pos = manager.pos + + if self.transition_axis == "z": + if self.ih not in self._s_map.keys(): + # Save scale instructions. + with self.screen_in.canvas.before: + PushMatrix() + self._s_map[self.ih] = Scale() + with self.screen_in.canvas.after: + PopMatrix() + with self.screen_out.canvas.before: + PushMatrix() + self._s_map[self.oh] = Scale() + with self.screen_out.canvas.after: + PopMatrix() + + self._s_map[self.oh].origin = [ + (manager.pos[0] + manager.width) / 2, + (manager.pos[1] + manager.height) / 2, + ] + self._s_map[self.ih].origin = self._s_map[self.oh].origin + # Relative subtracted area. + self._slide_diff = (manager.width - self.slide_distance) * ( + manager.height - self.slide_distance + ) / (manager.width * manager.height) - 1 + elif self.transition_axis in ["x", "y"]: + # Slide distance with opposite logic. + self._slide_diff = ( + (1 if self.opposite else -1) * self.slide_distance * 2 + ) + super().start(manager) + + def on_progress(self, progress): + # This code could be simplyfied with setattr, but it's slow + progress = getattr(MDAnimationTransition, self.switch_animation)(progress) + progress_i = progress - 1 + progress_d = progress * 2 + # First half. + if progress <= 0.5: + # Screen out animation. + if self.transition_axis == "z": + self._s_map[self.oh].xyz = ( + *[1 + self._slide_diff * progress_d] * 2, + 1, + ) + elif self.transition_axis == "x": + self.screen_out.pos = [ + self.manager.pos[0] + self._slide_diff * progress, + self.manager.pos[1], + ] + else: + self.screen_out.pos = [ + self.manager.pos[0], + self.manager.pos[1] - self._slide_diff * progress, + ] + self.screen_out.opacity = 1 - progress_d + self.screen_in.opacity = 0 + # Second half. + else: + if self.transition_axis == "z": + self._s_map[self.ih].xyz = ( + *[1 - self._slide_diff * progress_i * 2] * 2, + 1, + ) + elif self.transition_axis == "x": + self.screen_in.pos = [ + self.manager.pos[0] + self._slide_diff * progress_i, + self.manager.pos[1], + ] + else: + self.screen_in.pos = [ + self.manager.pos[0], + self.manager.pos[1] - self._slide_diff * progress_i, + ] + self.screen_in.opacity = progress_d - 1 + self.screen_out.opacity = 0 + + def on_complete(self): + self.screen_in.pos = self.manager.pos + self.screen_out.pos = self.manager.pos + self.screen_out.opacity = 1 + self.screen_in.opacity = 1 + if self.oh in self._s_map.keys(): + self._s_map[self.oh].xyz = (1, 1, 1) + if self.ih in self._s_map.keys(): + self._s_map[self.ih].xyz = (1, 1, 1) + super().on_complete() diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/widget.py b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/widget.py index 27c1371..4107bd0 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/uix/widget.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/uix/widget.py @@ -16,7 +16,7 @@ Widget canvas: Color: - rgba: app.theme_cls.primary_color + rgba: app.theme_cls.primaryColor RoundedRectangle: pos: self.pos size: self.size @@ -31,7 +31,7 @@ MDWidget size_hint: .5, None height: self.width radius: self.height / 2 - md_bg_color: app.theme_cls.primary_color + md_bg_color: app.theme_cls.primaryColor """ __all__ = ("MDWidget",) @@ -40,12 +40,26 @@ from kivy.uix.widget import Widget from kivymd.theming import ThemableBehavior from kivymd.uix import MDAdaptiveWidget -from kivymd.uix.behaviors import DeclarativeBehavior +from kivymd.uix.behaviors import DeclarativeBehavior, BackgroundColorBehavior -class MDWidget(DeclarativeBehavior, ThemableBehavior, MDAdaptiveWidget, Widget): +class MDWidget( + DeclarativeBehavior, + ThemableBehavior, + BackgroundColorBehavior, + MDAdaptiveWidget, + Widget, +): """ - See :class:`~kivy.uix.Widget` class documentation for more information. + Widget class. + + For more information, see in the + :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and + :class:`~kivymd.theming.ThemableBehavior` and + :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and + :class:`~kivymd.uix.MDAdaptiveWidget` and + :class:`~kivy.uix.widget.Widget` and + classes documentation. .. versionadded:: 1.0.0 """ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/__init__.cpython-311.pyc index cec0ae0..0f6b6c9 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/__init__.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/asynckivy.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/asynckivy.cpython-311.pyc deleted file mode 100644 index aee297d..0000000 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/asynckivy.cpython-311.pyc and /dev/null differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/fpsmonitor.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/fpsmonitor.cpython-311.pyc index 6e365f9..3f2be31 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/fpsmonitor.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/fpsmonitor.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/set_bars_colors.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/set_bars_colors.cpython-311.pyc index 7b54946..8f4daa7 100644 Binary files a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/set_bars_colors.cpython-311.pyc and b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/__pycache__/set_bars_colors.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/asynckivy.py b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/asynckivy.py deleted file mode 100644 index 373ab0b..0000000 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/asynckivy.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -asynckivy -========= - -Copyright (c) 2019 Nattōsai Mitō - -GitHub - - https://github.com/gottadiveintopython -GitHub Gist - - https://gist.github.com/gottadiveintopython/5f4a775849f9277081c396de65dc57c1 - -""" - -__all__ = ("start", "sleep", "event") - -import types -from collections import namedtuple -from functools import partial - -from kivy.clock import Clock - -CallbackParameter = namedtuple("CallbackParameter", ("args", "kwargs")) - - -def start(coro): - def step(*args, **kwargs): - try: - coro.send(CallbackParameter(args, kwargs))(step) - except StopIteration: - pass - - try: - coro.send(None)(step) - except StopIteration: - pass - - -@types.coroutine -def sleep(duration): - # The partial() here looks meaningless. But this is needed in order - # to avoid weak reference. - param = yield lambda step_coro: Clock.schedule_once( - partial(step_coro), duration - ) - return param.args[0] - - -class event: - def __init__(self, ed, name): - self.bind_id = None - self.ed = ed - self.name = name - - def bind(self, step_coro): - self.bind_id = bind_id = self.ed.fbind(self.name, self.callback) - assert bind_id > 0 # check if binding succeeded - self.step_coro = step_coro - - def callback(self, *args, **kwargs): - self.parameter = CallbackParameter(args, kwargs) - ed = self.ed - ed.unbind_uid(self.name, self.bind_id) - self.step_coro() - - def __await__(self): - yield self.bind - return self.parameter diff --git a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/fpsmonitor.py b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/fpsmonitor.py index 4fbc2fc..036d3f9 100644 --- a/kivy_venv/lib/python3.11/site-packages/kivymd/utils/fpsmonitor.py +++ b/kivy_venv/lib/python3.11/site-packages/kivymd/utils/fpsmonitor.py @@ -21,10 +21,11 @@ Builder.load_string( height: self.texture_size[1] text: root._fsp_value pos_hint: {root.anchor: 1} + color: app.theme_cls.surfaceColor canvas.before: Color: - rgba: app.theme_cls.primary_dark + rgba: app.theme_cls.onBackgroundColor Rectangle: pos: self.pos size: self.size @@ -33,16 +34,36 @@ Builder.load_string( class FpsMonitor(Label): + """ + Fps monitor class. + + For more information, see in the + :class:`~kivy.uix.label.Label` class documentation. + """ + updated_interval = NumericProperty(0.5) - """FPS refresh rate.""" + """ + FPS refresh rate. + + :attr:`updated_interval` is an :class:`~kivy.properties.NumericProperty` + and defaults to `0.5`. + """ anchor = OptionProperty("top", options=["top", "bottom"]) - """Monitor position.""" + """ + Monitor position. + Available option are: 'top', 'bottom'. + + :attr:`anchor` is an :class:`~kivy.properties.OptionProperty` + and defaults to `'top'`. + """ _fsp_value = StringProperty() - def start(self): + def start(self) -> None: + """Monitor starting.""" + Clock.schedule_interval(self.update_fps, self.updated_interval) - def update_fps(self, *args): + def update_fps(self, *args) -> None: self._fsp_value = "FPS: %f" % Clock.get_fps() diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/INSTALLER b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/LICENSE b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/LICENSE new file mode 100644 index 0000000..698b12e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Ansh Dadwal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/METADATA b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/METADATA new file mode 100644 index 0000000..0df37ff --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/METADATA @@ -0,0 +1,177 @@ +Metadata-Version: 2.1 +Name: materialyoucolor +Version: 2.0.9 +Summary: Material You color generation algorithms in pure python! +Author: Ansh Dadwal +Author-email: anshdadwal298@gmail.com +Description-Content-Type: text/markdown +License-File: LICENSE + +![image](https://github.com/T-Dynamos/materialyoucolor-pyhton/assets/68729523/b29c17d1-6c02-4c07-9a72-5b0198034760) + +# [Material You color algorithms](https://m3.material.io/styles/color/overview) for python! +It is built in reference with offical [typescript implementation](https://github.com/material-foundation/material-color-utilities/tree/main/typescript) except it's color quantization part, which is based on [c++ implementation](https://github.com/material-foundation/material-color-utilities/tree/main/cpp) thanks to [pybind](https://github.com/pybind). + +## Features + +1. Up to date with `material-foundation/material-color-utilities`. +2. Uses official c++ sources for quantization backend, which makes color generation fast! + +## Minimal running example: + +Run file `tests/test_all.py` as: + +```console +python3 test_all.py + +``` +Maximum quality is `1` that means use all pixels, and quality number more than `1` means how many pixels to skip in between while reading, also you can see it as compression. + +
+ Click to view result + +[Image Used, size was 8MB](https://unsplash.com/photos/zFMbpChjZGg/) + +![image](https://github.com/T-Dynamos/materialyoucolor-pyhton/assets/68729523/9d5374c9-00b4-4b70-b82a-6792dd5c910f) +![image](https://github.com/T-Dynamos/materialyoucolor-pyhton/assets/68729523/2edd819f-8600-4c82-a18a-3b759f63a552) + + +
+ + +## Usage + +### Install + +You can easily install it from pip by executing: +```console +pip3 install materialyoucolor --upgrade +``` +Prebuilt binaries are avaliable for `linux`, `windows` and `macos`. If prebuilt binaries aren't available, then you should manually build and install. + + +### Build and install + +```console +# Install +pip3 install https://github.com/T-Dynamos/materialyoucolor-python/archive/master.zip + +``` +### OS Specific + +#### Arch Linux + +```console +yay -S python-materialyoucolor +``` + +Thanks :heart: to [@midn8hustlr](https://github.com/midn8hustlr) for this [AUR package](https://aur.archlinux.org/cgit/aur.git/?h=python-materialyoucolor-git). + +#### Android (using kivy's [`buildozer`](https://github.com/kivy/buildozer)) + +Ensure these lines in `buildozer.spec`: +```python +requirements = materialyoucolor +p4a.branch = develop +``` + +#### IOS (using kivy's [`kivy-ios`](https://github.com/kivy/kivy-ios)) + +Install latest version of kivy-ios and use as: +```console +toolchain build materialyoucolor +``` + +## Usage examples +
+ Click to show + +- Generate non dynamic colors + +```python +from materialyoucolor.scheme import Scheme +from materialyoucolor.scheme.scheme_android import SchemeAndroid + +# Color is a an int, which is made as: +# 0xff + hex_code (without #) +# Eg: 0xff + #4181EE = 0xff4181EE +# To convert hex to this form, do `int("0xff" + "", 16)` +color = 0xff4181EE + +print(Scheme.light(color).props) +print(Scheme.dark(color).props) +# Props is a dict, key is color name and value is rgba format list +# {'primary': [0, 90, 195, 255], 'onPrimary': .... + +# Same way for android +print(SchemeAndroid.light(color).props) +print(SchemeAndroid.dark(color).props) +``` + +- Generate dynamic colors +```python +# Color in hue, chroma, tone form +from materialyoucolor.hct import Hct +from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors + +# There are 9 different variants of scheme. +from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot +# Others you can import: SchemeExpressive, SchemeFruitSalad, SchemeMonochrome, SchemeRainbow, SchemeVibrant, SchemeNeutral, SchemeFidelity and SchemeContent + +# SchemeTonalSpot is android default +scheme = SchemeTonalSpot( # choose any scheme here + Hct.from_int(0xff4181EE), # source color in hct form + True, # dark mode + 0.0, # contrast +) + +for color in vars(MaterialDynamicColors).keys(): + color_name = getattr(MaterialDynamicColors, color) + if hasattr(color_name, "get_hct"): # is a color + print(color, color_name.get_hct(scheme).to_rgba()) # print name of color and value in rgba format + +# background [14, 20, 21, 255] +# onBackground [222, 227, 229, 255] +# surface [14, 20, 21, 255] +# surfaceDim [14, 20, 21, 255] +# ... +``` + +- Generate and score colors from image + +```python +# Pillow is required to open image to array of pixels +from PIL import Image +# C++ QuantizeCelebi +from materialyoucolor.quantize import QuantizeCelebi +# Material You's default scoring of colors +from materialyoucolor.score.score import Score + +# Open image +image = Image.open("path_to_some_image.jpg") +pixel_len = image.width * image.height +image_data = image.getdata() + +# Quality 1 means skip no pixels +quality = 1 +pixel_array = [image_data[_] for _ in range(0, pixel_len, quality)] + +# Run algorithm +result = QuantizeCelebi(pixel_array, 128) # 128 -> number desired colors, default 128 +print(result) +# {4278722365: 2320, 4278723396: 2405, 4278723657: 2366,... +# result is a dict where key is +# color in integer form (which you can convert later), and value is population + +print(Score.score(result)) +# [4278722365, 4278723657] +# list of selected colors in integer form + +``` +
+ +## FAQ + +1. How it is different from `avanisubbiah/material-color-utilities`? + +See https://github.com/T-Dynamos/materialyoucolor-python/issues/3 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/RECORD b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/RECORD new file mode 100644 index 0000000..6b74ec7 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/RECORD @@ -0,0 +1,95 @@ +materialyoucolor-2.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +materialyoucolor-2.0.9.dist-info/LICENSE,sha256=lppbe_pW6cJdcUlYaF_zRzBswrJqs1gomT4N7JbmJsM,1068 +materialyoucolor-2.0.9.dist-info/METADATA,sha256=XXIfdJa6CbuS8hpspojDzPl-AIsfiQ6wroAjjOCmvAI,5503 +materialyoucolor-2.0.9.dist-info/RECORD,, +materialyoucolor-2.0.9.dist-info/WHEEL,sha256=YRqF-YRr2CS0FIhetXzBIHwB5RJ-VwbpK0NjjNHGNiw,153 +materialyoucolor-2.0.9.dist-info/top_level.txt,sha256=F0DiQDi7C25B2KuGjSImUsrxb1Qg60EkePEphUTDSO4,17 +materialyoucolor/__init__.py,sha256=pZib55qStLeBfZabni1F1OCNxdT02xP-5e34LjIBswQ,22 +materialyoucolor/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/blend/__init__.py,sha256=SibG2MG5iDjfdWVxNhjKCZnktCUP790XZBd_u1PorOw,25 +materialyoucolor/blend/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/blend/__pycache__/blend.cpython-311.pyc,, +materialyoucolor/blend/blend.py,sha256=MMn5SFJs7cMUWM9AZucCaLTXQHV2mIFYxOoCJY-GY9Q,1708 +materialyoucolor/contrast/__init__.py,sha256=uHrrUYMkvFlnGmcKOgNCmGUnO4xoesYqKjbXXwQOrd8,31 +materialyoucolor/contrast/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/contrast/__pycache__/contrast.cpython-311.pyc,, +materialyoucolor/contrast/contrast.py,sha256=hkvTvx9sWE2VW9MbD4ZCxOUJ8QCl-UxxAPeovV0G2Lc,2051 +materialyoucolor/dislike/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +materialyoucolor/dislike/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/dislike/__pycache__/dislike_analyzer.cpython-311.pyc,, +materialyoucolor/dislike/dislike_analyzer.py,sha256=LClByOVe0Kndc4wl0E2PMLVMzSKOkvaUJG4EcgFXncs,593 +materialyoucolor/dynamiccolor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +materialyoucolor/dynamiccolor/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/dynamiccolor/__pycache__/contrast_curve.cpython-311.pyc,, +materialyoucolor/dynamiccolor/__pycache__/dynamic_color.cpython-311.pyc,, +materialyoucolor/dynamiccolor/__pycache__/material_dynamic_colors.cpython-311.pyc,, +materialyoucolor/dynamiccolor/__pycache__/tone_delta_pair.cpython-311.pyc,, +materialyoucolor/dynamiccolor/contrast_curve.py,sha256=9bHeDVjVyujkGOuUlea91Ow-mKkPo8sqje7f2VILnuA,754 +materialyoucolor/dynamiccolor/dynamic_color.py,sha256=sEj3sVuhj1Ds1MytLv7bZD6n4onVUlSoEauXH4oUDEA,9607 +materialyoucolor/dynamiccolor/material_dynamic_colors.py,sha256=Yk-whQo9xUgWl0m7AX0M5dcBDxikiLMWkhIK_1tPjdM,26200 +materialyoucolor/dynamiccolor/tone_delta_pair.py,sha256=clkMwH7nWzCl1s9ju_m7_zZsGa9aALSgA0Xd5V7Jc08,471 +materialyoucolor/hct/__init__.py,sha256=m6iPRyKzzaP-QjqJZvWQOuBKYF1KavwXZMoMr-PXA0c,21 +materialyoucolor/hct/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/hct/__pycache__/cam16.cpython-311.pyc,, +materialyoucolor/hct/__pycache__/hct.cpython-311.pyc,, +materialyoucolor/hct/__pycache__/hct_solver.cpython-311.pyc,, +materialyoucolor/hct/__pycache__/viewing_conditions.cpython-311.pyc,, +materialyoucolor/hct/cam16.py,sha256=FR3qvQZ_LTpUTvvzaaWXQM3Jh8A_paIb1BKkiGXVIHo,12367 +materialyoucolor/hct/hct.py,sha256=ymIVnzYxLoc3m7UovtRBpiFLv5J1LH74J7slE-ZQNjA,2535 +materialyoucolor/hct/hct_solver.py,sha256=4wjPrVC5EFhbohaE7dschOKXNzh7RHWRGdewWBZQSC4,18185 +materialyoucolor/hct/viewing_conditions.py,sha256=9stpByAZSCYuxW4p1rQGqtqKKbEI-lSPWPu_3gWSLB4,2486 +materialyoucolor/palettes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +materialyoucolor/palettes/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/palettes/__pycache__/core_palette.cpython-311.pyc,, +materialyoucolor/palettes/__pycache__/tonal_palette.cpython-311.pyc,, +materialyoucolor/palettes/core_palette.py,sha256=ArJzhhBtGfWC07X1BvWtMgsKbzjIzCzeT5Cf933OTZM,2995 +materialyoucolor/palettes/tonal_palette.py,sha256=XAsC1xHAe75bu_IFiEmFQF2G0f5sDKJSN7RrUfxrs0M,2050 +materialyoucolor/quantize/__init__.py,sha256=F8fr7CKXzajNb0emnj8Ny5M5T2gfz5Luek2V9EvYJ1Q,48 +materialyoucolor/quantize/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/quantize/celebi.cpython-311-x86_64-linux-gnu.so,sha256=Fw3Yor7bst22eIoqO8_xh11o5OQ1LhK9SN275CRG6TM,351144 +materialyoucolor/scheme/__init__.py,sha256=EsukSJt4VIo7FaDhhTftNnApIsQxd5qXYrsFPUFFI3Q,27 +materialyoucolor/scheme/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/dynamic_scheme.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_android.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_content.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_expressive.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_fidelity.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_fruit_salad.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_monochrome.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_neutral.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_rainbow.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_tonal_spot.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/scheme_vibrant.cpython-311.pyc,, +materialyoucolor/scheme/__pycache__/variant.cpython-311.pyc,, +materialyoucolor/scheme/dynamic_scheme.py,sha256=LiTkA-znqQXeM7KJRDz-TK3Xv66R2i1mHcCkxsdTb9M,2369 +materialyoucolor/scheme/scheme.py,sha256=J4UUPvwz8RmQMigKg4jC1Iis36T1H88x1lIhIoYOrao,4391 +materialyoucolor/scheme/scheme_android.py,sha256=YW8Dqo9wQMnc_wgs4r-w1xF8VAxxKyM8FAVCOKDTHA8,4193 +materialyoucolor/scheme/scheme_content.py,sha256=WDSrhqr-crA3C8IwhwQtCF655wTGbru0LK01LY8Ps8s,1654 +materialyoucolor/scheme/scheme_expressive.py,sha256=hBhLSH-Czdl0HNXfEK99Md2b7VN_Ye3RQOW39F7EYYc,2008 +materialyoucolor/scheme/scheme_fidelity.py,sha256=AnxJ-H0MOiEJwRILUrfqQsYjgiUOo1N8XBGE12hQ1zA,1650 +materialyoucolor/scheme/scheme_fruit_salad.py,sha256=s793340qOt_LoaYaU2czGvUVnu0d9IPK6mb1_GE6V98,1387 +materialyoucolor/scheme/scheme_monochrome.py,sha256=PZsPofRBYFTYck4pyeu222Dft60K9t0oOUa1O_KlBag,1247 +materialyoucolor/scheme/scheme_neutral.py,sha256=e9rsWA6I1Rgdv9Jn-V9LWPE7apL7DPoI6AWfL6q9xag,1242 +materialyoucolor/scheme/scheme_rainbow.py,sha256=Y9QCrWG4paoerYPBrfPcFfRChI_trF52Eg3xJLkiHr4,1346 +materialyoucolor/scheme/scheme_tonal_spot.py,sha256=k8GuOTMcHv4rNw681zalMB8Cmdkpv3-4lfp2qUKMhtE,1372 +materialyoucolor/scheme/scheme_vibrant.py,sha256=Qz5_Ncn41ukbRPg1I4H_TsUNZbaL4Q8K33jHZBv_ntI,1875 +materialyoucolor/scheme/variant.py,sha256=VEwz04-CqfR4nqjxTiOgdxlyPxJ7zr4-YgK-QsT0wEQ,257 +materialyoucolor/score/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +materialyoucolor/score/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/score/__pycache__/score.cpython-311.pyc,, +materialyoucolor/score/score.py,sha256=CKNLsHDCSrgEQsmeIFTItZJR_l8M5AuSj8uJaEi2T3g,3897 +materialyoucolor/temperature/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +materialyoucolor/temperature/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/temperature/__pycache__/temperature_cache.cpython-311.pyc,, +materialyoucolor/temperature/temperature_cache.py,sha256=e39wBs6uoNaD543LkvmpvEW3fbJrLMRv076t2asGMdU,7235 +materialyoucolor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +materialyoucolor/utils/__pycache__/__init__.cpython-311.pyc,, +materialyoucolor/utils/__pycache__/color_utils.cpython-311.pyc,, +materialyoucolor/utils/__pycache__/math_utils.cpython-311.pyc,, +materialyoucolor/utils/__pycache__/platform_utils.cpython-311.pyc,, +materialyoucolor/utils/__pycache__/theme_utils.cpython-311.pyc,, +materialyoucolor/utils/color_utils.py,sha256=BVqV666pj09WPcExk9CvMxNvMW39tNBMFVc1jPtzems,5354 +materialyoucolor/utils/math_utils.py,sha256=erZe3CbgdelKkMUYtaPkKzJynEi-llEWnv-Nw3uC4tc,1592 +materialyoucolor/utils/platform_utils.py,sha256=_tjeBtbW53BoZ2moHXXrG3qbk-d2aduQ3zH0ZwBQCRM,11148 +materialyoucolor/utils/theme_utils.py,sha256=sF0aVyR14xd8akxC62ymMbGtXX8cU0tgT_5hnWxlnRE,2191 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/WHEEL b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/WHEEL new file mode 100644 index 0000000..faf21cf --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.43.0) +Root-Is-Purelib: false +Tag: cp311-cp311-manylinux_2_27_x86_64 +Tag: cp311-cp311-manylinux_2_28_x86_64 + diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/top_level.txt b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/top_level.txt new file mode 100644 index 0000000..e0b6b8a --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor-2.0.9.dist-info/top_level.txt @@ -0,0 +1 @@ +materialyoucolor diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/__init__.py new file mode 100644 index 0000000..e5d18b2 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/__init__.py @@ -0,0 +1 @@ +__version__ = "2.0.9" diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..a9b6ac0 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__init__.py new file mode 100644 index 0000000..6046c38 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__init__.py @@ -0,0 +1 @@ +from .blend import Blend diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..8cd7a1b Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__pycache__/blend.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__pycache__/blend.cpython-311.pyc new file mode 100644 index 0000000..851c955 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/__pycache__/blend.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/blend.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/blend.py new file mode 100644 index 0000000..07f04e4 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/blend/blend.py @@ -0,0 +1,45 @@ +from materialyoucolor.hct import Hct +from materialyoucolor.hct.cam16 import Cam16 +from materialyoucolor.utils.math_utils import ( + sanitize_degrees_double, + difference_degrees, + rotation_direction, +) +from materialyoucolor.utils.color_utils import lstar_from_argb + + +class Blend: + @staticmethod + def harmonize(design_color: int, source_color: int) -> int: + from_hct = Hct.from_int(design_color) + to_hct = Hct.from_int(source_color) + difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue) + rotation_degrees = min(difference_degrees_ * 0.5, 15.0) + output_hue = sanitize_degrees_double( + from_hct.hue + + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue) + ) + return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int() + + @staticmethod + def hct_hue(from_: int, to: int, amount: int) -> int: + ucs = Blend.cam16_ucs(from_, to, amount) + ucs_cam = Cam16.from_int(ucs) + from_cam = Cam16.from_int(from_) + blended = Hct.from_hct(ucs_cam.hue, from_cam.chroma, lstar_from_argb(from_)) + return blended.to_int() + + @staticmethod + def cam16_ucs(from_: int, to: int, amount: float) -> int: + from_cam = Cam16.from_int(from_) + to_cam = Cam16.from_int(to) + from_j = from_cam.jstar + from_a = from_cam.astar + from_b = from_cam.bstar + to_j = to_cam.jstar + to_a = to_cam.astar + to_b = to_cam.bstar + jstar = from_j + (to_j - from_j) * amount + astar = from_a + (to_a - from_a) * amount + bstar = from_b + (to_b - from_b) * amount + return Cam16.from_ucs(jstar, astar, bstar).to_int() diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__init__.py new file mode 100644 index 0000000..0e73a2a --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__init__.py @@ -0,0 +1 @@ +from .contrast import Contrast diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..1771cc7 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__pycache__/contrast.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__pycache__/contrast.cpython-311.pyc new file mode 100644 index 0000000..e01913a Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/__pycache__/contrast.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/contrast.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/contrast.py new file mode 100644 index 0000000..5124410 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/contrast/contrast.py @@ -0,0 +1,61 @@ +from materialyoucolor.utils.math_utils import clamp_double +from materialyoucolor.utils.color_utils import y_from_lstar, lstar_from_y + + +class Contrast: + @staticmethod + def ratio_of_tones(tone_a: float, tone_b: float) -> float: + tone_a = clamp_double(0.0, 100.0, tone_a) + tone_b = clamp_double(0.0, 100.0, tone_b) + return Contrast.ratio_of_ys(y_from_lstar(tone_a), y_from_lstar(tone_b)) + + @staticmethod + def ratio_of_ys(y1: float, y2: float) -> float: + lighter = y1 if y1 > y2 else y2 + darker = y1 if lighter == y2 else y2 + return (lighter + 5.0) / (darker + 5.0) + + @staticmethod + def lighter(tone: float, ratio: float) -> float: + if tone < 0.0 or tone > 100.0: + return -1.0 + + dark_y = y_from_lstar(tone) + light_y = ratio * (dark_y + 5.0) - 5.0 + real_contrast = Contrast.ratio_of_ys(light_y, dark_y) + delta = abs(real_contrast - ratio) + if real_contrast < ratio and delta > 0.04: + return -1 + + return_value = lstar_from_y(light_y) + 0.4 + if return_value < 0 or return_value > 100: + return -1.0 + return return_value + + @staticmethod + def darker(tone: float, ratio: float) -> float: + if tone < 0.0 or tone > 100.0: + return -1.0 + + light_y = y_from_lstar(tone) + dark_y = (light_y + 5.0) / ratio - 5.0 + real_contrast = Contrast.ratio_of_ys(light_y, dark_y) + + delta = abs(real_contrast - ratio) + if real_contrast < ratio and delta > 0.04: + return -1 + + return_value = lstar_from_y(dark_y) - 0.4 + if return_value < 0 or return_value > 100: + return -1 + return return_value + + @staticmethod + def lighter_unsafe(tone, ratio): + lighter_safe = Contrast.lighter(tone, ratio) + return 100.0 if lighter_safe < 0.0 else lighter_safe + + @staticmethod + def darker_unsafe(tone, ratio): + darker_safe = Contrast.darker(tone, ratio) + return 0.0 if darker_safe < 0.0 else darker_safe diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..09c389c Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__pycache__/dislike_analyzer.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__pycache__/dislike_analyzer.cpython-311.pyc new file mode 100644 index 0000000..8621c10 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/__pycache__/dislike_analyzer.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/dislike_analyzer.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/dislike_analyzer.py new file mode 100644 index 0000000..8868d20 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dislike/dislike_analyzer.py @@ -0,0 +1,21 @@ +from materialyoucolor.hct import Hct + + +class DislikeAnalyzer: + @staticmethod + def is_disliked(hct: Hct) -> bool: + hue_passes = round(hct.hue) >= 90.0 and round(hct.hue) <= 111.0 + chroma_passes = round(hct.chroma) > 16.0 + tone_passes = round(hct.tone) < 65.0 + return hue_passes and chroma_passes and tone_passes + + @staticmethod + def fix_if_disliked(hct: Hct) -> Hct: + if DislikeAnalyzer.is_disliked(hct): + return Hct.from_hct( + hct.hue, + hct.chroma, + 70.0, + ) + + return hct diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..b37c48d Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/contrast_curve.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/contrast_curve.cpython-311.pyc new file mode 100644 index 0000000..7679f26 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/contrast_curve.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/dynamic_color.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/dynamic_color.cpython-311.pyc new file mode 100644 index 0000000..bceeeb2 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/dynamic_color.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/material_dynamic_colors.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/material_dynamic_colors.cpython-311.pyc new file mode 100644 index 0000000..aabfff4 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/material_dynamic_colors.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/tone_delta_pair.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/tone_delta_pair.cpython-311.pyc new file mode 100644 index 0000000..9d66747 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/__pycache__/tone_delta_pair.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/contrast_curve.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/contrast_curve.py new file mode 100644 index 0000000..0402585 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/contrast_curve.py @@ -0,0 +1,21 @@ +from materialyoucolor.utils.math_utils import lerp + + +class ContrastCurve: + def __init__(self, low: float, normal: float, medium: float, high: float): + self.low = low + self.normal = normal + self.medium = medium + self.high = high + + def get(self, contrast_level: float) -> float: + if contrast_level <= -1.0: + return self.low + elif contrast_level < 0.0: + return lerp(self.low, self.normal, (contrast_level - (-1)) / 1) + elif contrast_level < 0.5: + return lerp(self.normal, self.medium, (contrast_level - 0) / 0.5) + elif contrast_level < 1.0: + return lerp(self.medium, self.high, (contrast_level - 0.5) / 0.5) + else: + return self.high diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/dynamic_color.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/dynamic_color.py new file mode 100644 index 0000000..4f78349 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/dynamic_color.py @@ -0,0 +1,270 @@ +from materialyoucolor.contrast import Contrast +from materialyoucolor.hct import Hct +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.scheme.dynamic_scheme import DynamicScheme +from materialyoucolor.dynamiccolor.contrast_curve import ContrastCurve +from materialyoucolor.dynamiccolor.tone_delta_pair import ToneDeltaPair + + +class FromPaletteOptions: + def __init__( + self, + name=str, + palette=None, + tone=int, + is_background=bool, + background=None, + second_background=None, + contrast_curve=None, + tone_delta_pair=None, + ): + self.name = name + self.palette = palette + self.tone = tone + self.is_background = is_background + self.background = background + self.second_background = second_background + self.contrast_curve = contrast_curve + self.tone_delta_pair = tone_delta_pair + + +class DynamicColor: + hct_cache = dict[DynamicScheme, Hct] + name = str + palette = None + tone = int + is_background = bool + background = None + second_background = None + contrast_curve = None + tone_delta_pair = None + + def __init__( + self, + name=str, + palette=None, + tone=int, + is_background=bool, + background=None, + second_background=None, + contrast_curve=None, + tone_delta_pair=None, + ): + self.name = name + self.palette = palette + self.tone = tone + self.is_background = is_background + self.background = background + self.second_background = second_background + self.contrast_curve = contrast_curve + self.tone_delta_pair = tone_delta_pair + self.hct_cache = {} + if not self.background and self.second_background: + raise ValueError( + f"Color {name} has secondBackground defined, but background is not defined." + ) + if not self.background and self.contrast_curve: + raise ValueError( + f"Color {name} has contrastCurve defined, but background is not defined." + ) + if self.background and not self.contrast_curve: + raise ValueError( + f"Color {name} has background defined, but contrastCurve is not defined." + ) + + @staticmethod + def from_palette(args: FromPaletteOptions): + return DynamicColor( + args.name, + args.palette, + args.tone, + args.is_background, + args.background, + args.second_background, + args.contrast_curve, + args.tone_delta_pair, + ) + + def get_argb(self, scheme: DynamicScheme) -> int: + return self.get_hct(scheme).to_int() + + def get_hct(self, scheme: DynamicScheme) -> Hct: + cached_answer = self.hct_cache.get(scheme) + if cached_answer is not None: + return cached_answer + + tone = self.get_tone(scheme) + answer = self.palette(scheme).get_hct(tone) + if len(self.hct_cache) > 4: + self.hct_cache.clear() + self.hct_cache[scheme] = answer + return answer + + def get_tone(self, scheme): + decreasing_contrast = scheme.contrast_level < 0 + + if self.tone_delta_pair: + tone_delta_pair = self.tone_delta_pair(scheme) + role_a, role_b = tone_delta_pair.role_a, tone_delta_pair.role_b + delta, polarity, stay_together = ( + tone_delta_pair.delta, + tone_delta_pair.polarity, + tone_delta_pair.stay_together, + ) + + bg = self.background(scheme) + bg_tone = bg.get_tone(scheme) + + a_is_nearer = ( + polarity == "nearer" + or (polarity == "lighter" and not scheme.is_dark) + or (polarity == "darker" and scheme.is_dark) + ) + nearer, farther = (role_a, role_b) if a_is_nearer else (role_b, role_a) + am_nearer = self.name == nearer.name + expansion_dir = 1 if scheme.is_dark else -1 + + n_contrast = nearer.contrast_curve.get(scheme.contrast_level) + f_contrast = farther.contrast_curve.get(scheme.contrast_level) + + n_initial_tone = nearer.tone(scheme) + n_tone = ( + n_initial_tone + if Contrast.ratio_of_tones(bg_tone, n_initial_tone) >= n_contrast + else DynamicColor.foreground_tone(bg_tone, n_contrast) + ) + + f_initial_tone = farther.tone(scheme) + f_tone = ( + f_initial_tone + if Contrast.ratio_of_tones(bg_tone, f_initial_tone) >= f_contrast + else DynamicColor.foreground_tone(bg_tone, f_contrast) + ) + + if decreasing_contrast: + n_tone = DynamicColor.foreground_tone(bg_tone, n_contrast) + f_tone = DynamicColor.foreground_tone(bg_tone, f_contrast) + + if (f_tone - n_tone) * expansion_dir >= delta: + pass + else: + f_tone = ( + min(max(n_tone + delta * expansion_dir, 0), 100) + if (f_tone - n_tone) * expansion_dir >= delta + else min(max(f_tone - delta * expansion_dir, 0), 100) + ) + + if 50 <= n_tone < 60: + if expansion_dir > 0: + n_tone, f_tone = 60, max(f_tone, n_tone + delta * expansion_dir) + else: + n_tone, f_tone = 49, min(f_tone, n_tone + delta * expansion_dir) + elif 50 <= f_tone < 60: + if stay_together: + if expansion_dir > 0: + n_tone, f_tone = 60, max(f_tone, n_tone + delta * expansion_dir) + else: + n_tone, f_tone = 49, min(f_tone, n_tone + delta * expansion_dir) + else: + if expansion_dir > 0: + f_tone = 60 + else: + f_tone = 49 + + return n_tone if am_nearer else f_tone + + else: + answer = self.tone(scheme) + + if self.background is None: + return answer + + bg_tone = self.background(scheme).get_tone(scheme) + desired_ratio = self.contrast_curve.get(scheme.contrast_level) + + if Contrast.ratio_of_tones(bg_tone, answer) >= desired_ratio: + pass + else: + answer = DynamicColor.foreground_tone(bg_tone, desired_ratio) + + if decreasing_contrast: + answer = DynamicColor.foreground_tone(bg_tone, desired_ratio) + + if self.is_background and 50 <= answer < 60: + answer = ( + 49 if Contrast.ratio_of_tones(49, bg_tone) >= desired_ratio else 60 + ) + + if self.second_background: + bg1, bg2 = self.background, self.second_background + bg_tone1, bg_tone2 = bg1(scheme).get_tone(scheme), bg2(scheme).get_tone( + scheme + ) + upper, lower = max(bg_tone1, bg_tone2), min(bg_tone1, bg_tone2) + + if ( + Contrast.ratio_of_tones(upper, answer) >= desired_ratio + and Contrast.ratio_of_tones(lower, answer) >= desired_ratio + ): + return answer + + light_option = Contrast.lighter(upper, desired_ratio) + dark_option = Contrast.darker(lower, desired_ratio) + availables = [light_option] if light_option != -1 else [] + if dark_option != -1: + availables.append(dark_option) + + prefers_light = DynamicColor.tone_prefers_light_foreground( + bg_tone1 + ) or DynamicColor.tone_prefers_light_foreground(bg_tone2) + return ( + light_option + if prefers_light and (light_option == -1 or dark_option == -1) + else dark_option + ) + + return answer + + @staticmethod + def foreground_tone(bg_tone, ratio): + lighter_tone = Contrast.lighter_unsafe(bg_tone, ratio) + darker_tone = Contrast.darker_unsafe(bg_tone, ratio) + lighter_ratio = Contrast.ratio_of_tones(lighter_tone, bg_tone) + darker_ratio = Contrast.ratio_of_tones(darker_tone, bg_tone) + prefer_lighter = DynamicColor.tone_prefers_light_foreground(bg_tone) + + if prefer_lighter: + negligible_difference = ( + abs(lighter_ratio - darker_ratio) < 0.1 + and lighter_ratio < ratio + and darker_ratio < ratio + ) + return ( + lighter_tone + if lighter_ratio >= ratio + or lighter_ratio >= darker_ratio + or negligible_difference + else darker_tone + ) + else: + return ( + darker_tone + if darker_ratio >= ratio or darker_ratio >= lighter_ratio + else lighter_tone + ) + + @staticmethod + def tone_prefers_light_foreground(tone): + return round(tone) < 60.0 + + @staticmethod + def tone_allows_light_foreground(tone): + return round(tone) <= 49.0 + + @staticmethod + def enable_light_foreground(tone): + if DynamicColor.tone_prefers_light_foreground( + tone + ) and not DynamicColor.tone_allows_light_foreground(tone): + return 49.0 + return tone diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/material_dynamic_colors.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/material_dynamic_colors.py new file mode 100644 index 0000000..fdd96c6 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/material_dynamic_colors.py @@ -0,0 +1,744 @@ +from materialyoucolor.hct import Hct +from materialyoucolor.scheme.dynamic_scheme import DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.dynamiccolor.contrast_curve import ContrastCurve +from materialyoucolor.dynamiccolor.tone_delta_pair import ToneDeltaPair +from materialyoucolor.dynamiccolor.dynamic_color import FromPaletteOptions, DynamicColor +from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer + + +def is_fidelity(scheme: DynamicScheme) -> bool: + return scheme.variant == Variant.FIDELITY or scheme.variant == Variant.CONTENT + + +def is_monochrome(scheme: DynamicScheme) -> bool: + return scheme.variant == Variant.MONOCHROME + + +def find_desired_chroma_by_tone( + hue: float, chroma: float, tone: float, by_decreasing_tone: float +) -> float: + answer = tone + + closest_to_chroma = Hct.from_hct(hue, chroma, tone) + if closest_to_chroma.chroma < chroma: + chroma_peak = closest_to_chroma.chroma + while closest_to_chroma.chroma < chroma: + answer += -1.0 if by_decreasing_tone else 1.0 + potential_solution = Hct.from_hct(hue, chroma, answer) + if chroma_peak > potential_solution.chroma: + break + if abs(potential_solution.chroma - chroma) < 0.4: + break + + potential_delta = abs(potential_solution.chroma - chroma) + current_delta = abs(closest_to_chroma.chroma - chroma) + if potential_delta < current_delta: + closest_to_chroma = potential_solution + chroma_peak = max(chroma_peak, potential_solution.chroma) + + return answer + + +def find_desired_tone_for_sec(s): + initial_tone = 30 if s.is_dark else 90 + if is_monochrome(s): + return 30 if s.is_dark else 85 + if is_fidelity(s): + return initial_tone + return find_desired_chroma_by_tone( + s.secondary_palette.hue, + s.secondary_palette.chroma, + initial_tone, + False if s.is_dark else True, + ) + + +def tertiary_container_tone(s): + if not is_monochrome(s): + return 60 if s.is_dark else 49 + if not is_fidelity(s): + return 30 if s.is_dark else 90 + proposed_hct = s.tertiary_palette.get_hct(s.source_color_hct.tone) + return DislikeAnalyzer.fix_if_disliked(proposed_hct).tone + + +def on_tertiary_container_tone(s): + if not is_monochrome(s): + return 0 if s.is_dark else 100 + if not is_fidelity(s): + return 90 if s.is_dark else 10 + return DynamicColor.foreground_tone( + MaterialDynamicColors.tertiaryContainer.tone(s), 4.5 + ) + + +class MaterialDynamicColors: + content_accent_tone_delta = 15.0 + + @staticmethod + def highestSurface(s: DynamicScheme) -> DynamicColor: + return ( + MaterialDynamicColors.surfaceBright + if s.is_dark + else MaterialDynamicColors.surfaceDim + ) + + primary_paletteKeyColor = DynamicColor.from_palette( + FromPaletteOptions( + name="primary_palette_key_color", + palette=lambda s: s.primary_palette, + tone=lambda s: s.primary_palette.key_color.tone, + ) + ) + + secondary_paletteKeyColor = DynamicColor.from_palette( + FromPaletteOptions( + name="secondary_palette_key_color", + palette=lambda s: s.secondary_palette, + tone=lambda s: s.secondary_palette.key_color.tone, + ) + ) + + tertiary_paletteKeyColor = DynamicColor.from_palette( + FromPaletteOptions( + name="tertiary_palette_key_color", + palette=lambda s: s.tertiary_palette, + tone=lambda s: s.tertiary_palette.key_color.tone, + ) + ) + + neutral_paletteKeyColor = DynamicColor.from_palette( + FromPaletteOptions( + name="neutral_palette_key_color", + palette=lambda s: s.neutral_palette, + tone=lambda s: s.neutral_palette.key_color.tone, + ) + ) + + neutral_variant_paletteKeyColor = DynamicColor.from_palette( + FromPaletteOptions( + name="neutral_variant_palette_key_color", + palette=lambda s: s.neutral_variant_palette, + tone=lambda s: s.neutral_variant_palette.key_color.tone, + ) + ) + + background = DynamicColor.from_palette( + FromPaletteOptions( + name="background", + palette=lambda s: s.neutral_palette, + tone=lambda s: 6 if s.is_dark else 98, + is_background=True, + ) + ) + + onBackground = DynamicColor.from_palette( + FromPaletteOptions( + name="on_background", + palette=lambda s: s.neutral_palette, + tone=lambda s: 90 if s.is_dark else 10, + background=lambda s: MaterialDynamicColors.background, + contrast_curve=ContrastCurve(3, 3, 4.5, 7), + ) + ) + + surface = DynamicColor.from_palette( + FromPaletteOptions( + name="surface", + palette=lambda s: s.neutral_palette, + tone=lambda s: 6 if s.is_dark else 98, + is_background=True, + ) + ) + + surfaceDim = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_dim", + palette=lambda s: s.neutral_palette, + tone=lambda s: 6 + if s.is_dark + else ContrastCurve(87, 87, 80, 75).get(s.contrast_level), + is_background=True, + ) + ) + + surfaceBright = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_bright", + palette=lambda s: s.neutral_palette, + tone=lambda s: ContrastCurve(24, 24, 29, 34).get(s.contrast_level) + if s.is_dark + else 98, + is_background=True, + ) + ) + + surfaceContainerLowest = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_container_lowest", + palette=lambda s: s.neutral_palette, + tone=lambda s: ContrastCurve(4, 4, 2, 0).get(s.contrast_level) + if s.is_dark + else 100, + is_background=True, + ) + ) + + surfaceContainerLow = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_container_low", + palette=lambda s: s.neutral_palette, + tone=lambda s: ContrastCurve(10, 10, 11, 12).get(s.contrast_level) + if s.is_dark + else ContrastCurve(96, 96, 96, 95).get(s.contrast_level), + is_background=True, + ) + ) + + surfaceContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_container", + palette=lambda s: s.neutral_palette, + tone=lambda s: ContrastCurve(12, 12, 16, 20).get(s.contrast_level) + if s.is_dark + else ContrastCurve(94, 94, 92, 90).get(s.contrast_level), + is_background=True, + ) + ) + + surfaceContainerHigh = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_container_high", + palette=lambda s: s.neutral_palette, + tone=lambda s: ContrastCurve(17, 17, 21, 25).get(s.contrast_level) + if s.is_dark + else ContrastCurve(92, 92, 88, 85).get(s.contrast_level), + is_background=True, + ) + ) + + surfaceContainerHighest = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_container_highest", + palette=lambda s: s.neutral_palette, + tone=lambda s: ContrastCurve(22, 22, 26, 30).get(s.contrast_level) + if s.is_dark + else ContrastCurve(90, 90, 84, 80).get(s.contrast_level), + is_background=True, + ) + ) + + onSurface = DynamicColor.from_palette( + FromPaletteOptions( + name="on_surface", + palette=lambda s: s.neutral_palette, + tone=lambda s: 90 if s.is_dark else 10, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + surfaceVariant = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_variant", + palette=lambda s: s.neutral_variant_palette, + tone=lambda s: 30 if s.is_dark else 90, + is_background=True, + ) + ) + + onSurfaceVariant = DynamicColor.from_palette( + FromPaletteOptions( + name="on_surface_variant", + palette=lambda s: s.neutral_variant_palette, + tone=lambda s: 80 if s.is_dark else 30, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(3, 4.5, 7, 11), + ) + ) + + inverseSurface = DynamicColor.from_palette( + FromPaletteOptions( + name="inverse_surface", + palette=lambda s: s.neutral_palette, + tone=lambda s: 90 if s.is_dark else 20, + ) + ) + + inverseOnSurface = DynamicColor.from_palette( + FromPaletteOptions( + name="inverse_on_surface", + palette=lambda s: s.neutral_palette, + tone=lambda s: 20 if s.is_dark else 95, + background=lambda s: MaterialDynamicColors.inverseSurface, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + outline = DynamicColor.from_palette( + FromPaletteOptions( + name="outline", + palette=lambda s: s.neutral_variant_palette, + tone=lambda s: 60 if s.is_dark else 50, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1.5, 3, 4.5, 7), + ) + ) + + outlineVariant = DynamicColor.from_palette( + FromPaletteOptions( + name="outline_variant", + palette=lambda s: s.neutral_variant_palette, + tone=lambda s: 30 if s.is_dark else 80, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + ) + ) + + shadow = DynamicColor.from_palette( + FromPaletteOptions( + name="shadow", + palette=lambda s: s.neutral_palette, + tone=lambda s: 0, + ) + ) + + scrim = DynamicColor.from_palette( + FromPaletteOptions( + name="scrim", + palette=lambda s: s.neutral_palette, + tone=lambda s: 0, + ) + ) + + surfaceTint = DynamicColor.from_palette( + FromPaletteOptions( + name="surface_tint", + palette=lambda s: s.primary_palette, + tone=lambda s: 80 if s.is_dark else 40, + is_background=True, + ) + ) + + primary = DynamicColor.from_palette( + FromPaletteOptions( + name="primary", + palette=lambda s: s.primary_palette, + tone=lambda s: 100 if is_monochrome(s) else (80 if s.is_dark else 40), + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(3, 4.5, 7, 7), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.primaryContainer, + MaterialDynamicColors.primary, + 10, + "nearer", + False, + ), + ) + ) + + onPrimary = DynamicColor.from_palette( + FromPaletteOptions( + name="on_primary", + palette=lambda s: s.primary_palette, + tone=lambda s: 10 if is_monochrome(s) else (20 if s.is_dark else 100), + background=lambda s: MaterialDynamicColors.primary, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + primaryContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="primary_container", + palette=lambda s: s.primary_palette, + tone=lambda s: s.source_color_hct.tone + if is_fidelity(s) + else (85 if is_monochrome(s) else (30 if s.is_dark else 90)), + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.primaryContainer, + MaterialDynamicColors.primary, + 10, + "nearer", + False, + ), + ) + ) + + onPrimaryContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="on_primary_container", + palette=lambda s: s.primary_palette, + tone=lambda s: DynamicColor.foreground_tone( + MaterialDynamicColors.primaryContainer.tone(s), 4.5 + ) + if is_fidelity(s) + else (0 if is_monochrome(s) else (90 if s.is_dark else 10)), + background=lambda s: MaterialDynamicColors.primaryContainer, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + inversePrimary = DynamicColor.from_palette( + FromPaletteOptions( + name="inverse_primary", + palette=lambda s: s.primary_palette, + tone=lambda s: 40 if s.is_dark else 80, + background=lambda s: MaterialDynamicColors.inverseSurface, + contrast_curve=ContrastCurve(3, 4.5, 7, 7), + ) + ) + + secondary = DynamicColor.from_palette( + FromPaletteOptions( + name="secondary", + palette=lambda s: s.secondary_palette, + tone=lambda s: 80 if s.is_dark else 40, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(3, 4.5, 7, 7), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.secondaryContainer, + MaterialDynamicColors.secondary, + 10, + "nearer", + False, + ), + ) + ) + + onSecondary = DynamicColor.from_palette( + FromPaletteOptions( + name="on_secondary", + palette=lambda s: s.secondary_palette, + tone=lambda s: 10 if is_monochrome(s) else (20 if s.is_dark else 100), + background=lambda s: MaterialDynamicColors.secondary, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + secondaryContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="secondary_container", + palette=lambda s: s.secondary_palette, + tone=lambda s: find_desired_tone_for_sec(s), + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.secondaryContainer, + MaterialDynamicColors.secondary, + 10, + "nearer", + False, + ), + ) + ) + + onSecondaryContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="on_secondary_container", + palette=lambda s: s.secondary_palette, + tone=lambda s: (90 if s.is_dark else 10) + if not is_fidelity(s) + else DynamicColor.foreground_tone( + MaterialDynamicColors.secondaryContainer.tone(s), 4.5 + ), + background=lambda s: MaterialDynamicColors.secondaryContainer, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + tertiary = DynamicColor.from_palette( + FromPaletteOptions( + name="tertiary", + palette=lambda s: s.tertiary_palette, + tone=lambda s: (90 if s.is_dark else 25) + if is_monochrome(s) + else (80 if s.is_dark else 40), + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(3, 4.5, 7, 7), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.tertiaryContainer, + MaterialDynamicColors.tertiary, + 10, + "nearer", + False, + ), + ) + ) + + onTertiary = DynamicColor.from_palette( + FromPaletteOptions( + name="on_tertiary", + palette=lambda s: s.tertiary_palette, + tone=lambda s: (10 if s.is_dark else 90) + if is_monochrome(s) + else (20 if s.is_dark else 100), + background=lambda s: MaterialDynamicColors.tertiary, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + tertiaryContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="tertiary_container", + palette=lambda s: s.tertiary_palette, + tone=lambda s: tertiary_container_tone(s), + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.tertiaryContainer, + MaterialDynamicColors.tertiary, + 10, + "nearer", + False, + ), + ) + ) + + onTertiaryContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="on_tertiary_container", + palette=lambda s: s.tertiary_palette, + tone=lambda s: on_tertiary_container_tone(s), + background=lambda s: MaterialDynamicColors.tertiaryContainer, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + error = DynamicColor.from_palette( + FromPaletteOptions( + name="error", + palette=lambda s: s.error_palette, + tone=lambda s: 80 if s.is_dark else 40, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(3, 4.5, 7, 7), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.errorContainer, + MaterialDynamicColors.error, + 10, + "nearer", + False, + ), + ) + ) + + onError = DynamicColor.from_palette( + FromPaletteOptions( + name="on_error", + palette=lambda s: s.error_palette, + tone=lambda s: 20 if s.is_dark else 100, + background=lambda s: MaterialDynamicColors.error, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + errorContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="error_container", + palette=lambda s: s.error_palette, + tone=lambda s: 30 if s.is_dark else 90, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.errorContainer, + MaterialDynamicColors.error, + 10, + "nearer", + False, + ), + ) + ) + + onErrorContainer = DynamicColor.from_palette( + FromPaletteOptions( + name="on_error_container", + palette=lambda s: s.error_palette, + tone=lambda s: 90 if s.is_dark else 10, + background=lambda s: MaterialDynamicColors.errorContainer, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + primaryFixed = DynamicColor.from_palette( + FromPaletteOptions( + name="primary_fixed", + palette=lambda s: s.primary_palette, + tone=lambda s: 40.0 if is_monochrome(s) else 90.0, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.primaryFixed, + MaterialDynamicColors.primaryFixedDim, + 10, + "lighter", + True, + ), + ) + ) + + primaryFixedDim = DynamicColor.from_palette( + FromPaletteOptions( + name="primary_fixed_dim", + palette=lambda s: s.primary_palette, + tone=lambda s: 30.0 if is_monochrome(s) else 80.0, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.primaryFixed, + MaterialDynamicColors.primaryFixedDim, + 10, + "lighter", + True, + ), + ) + ) + + onPrimaryFixed = DynamicColor.from_palette( + FromPaletteOptions( + name="on_primary_fixed", + palette=lambda s: s.primary_palette, + tone=lambda s: 100.0 if is_monochrome(s) else 10.0, + background=lambda s: MaterialDynamicColors.primaryFixedDim, + second_background=lambda s: MaterialDynamicColors.primaryFixed, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + onPrimaryFixedVariant = DynamicColor.from_palette( + FromPaletteOptions( + name="on_primary_fixed_variant", + palette=lambda s: s.primary_palette, + tone=lambda s: 90.0 if is_monochrome(s) else 30.0, + background=lambda s: MaterialDynamicColors.primaryFixedDim, + second_background=lambda s: MaterialDynamicColors.primaryFixed, + contrast_curve=ContrastCurve(3, 4.5, 7, 11), + ) + ) + + secondaryFixed = DynamicColor.from_palette( + FromPaletteOptions( + name="secondary_fixed", + palette=lambda s: s.secondary_palette, + tone=lambda s: 80.0 if is_monochrome(s) else 90.0, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.secondaryFixed, + MaterialDynamicColors.secondaryFixedDim, + 10, + "lighter", + True, + ), + ) + ) + + secondaryFixedDim = DynamicColor.from_palette( + FromPaletteOptions( + name="secondary_fixed_dim", + palette=lambda s: s.secondary_palette, + tone=lambda s: 70.0 if is_monochrome(s) else 80.0, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.secondaryFixed, + MaterialDynamicColors.secondaryFixedDim, + 10, + "lighter", + True, + ), + ) + ) + + onSecondaryFixed = DynamicColor.from_palette( + FromPaletteOptions( + name="on_secondary_fixed", + palette=lambda s: s.secondary_palette, + tone=lambda s: 10.0, + background=lambda s: MaterialDynamicColors.secondaryFixedDim, + second_background=lambda s: MaterialDynamicColors.secondaryFixed, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + onSecondaryFixedVariant = DynamicColor.from_palette( + FromPaletteOptions( + name="on_secondary_fixed_variant", + palette=lambda s: s.secondary_palette, + tone=lambda s: 25.0 if is_monochrome(s) else 30.0, + background=lambda s: MaterialDynamicColors.secondaryFixedDim, + second_background=lambda s: MaterialDynamicColors.secondaryFixed, + contrast_curve=ContrastCurve(3, 4.5, 7, 11), + ) + ) + + tertiaryFixed = DynamicColor.from_palette( + FromPaletteOptions( + name="tertiary_fixed", + palette=lambda s: s.tertiary_palette, + tone=lambda s: 40.0 if is_monochrome(s) else 90.0, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.tertiaryFixed, + MaterialDynamicColors.tertiaryFixedDim, + 10, + "lighter", + True, + ), + ) + ) + + tertiaryFixedDim = DynamicColor.from_palette( + FromPaletteOptions( + name="tertiary_fixed_dim", + palette=lambda s: s.tertiary_palette, + tone=lambda s: 30.0 if is_monochrome(s) else 80.0, + is_background=True, + background=lambda s: MaterialDynamicColors.highestSurface(s), + contrast_curve=ContrastCurve(1, 1, 3, 4.5), + tone_delta_pair=lambda s: ToneDeltaPair( + MaterialDynamicColors.tertiaryFixed, + MaterialDynamicColors.tertiaryFixedDim, + 10, + "lighter", + True, + ), + ) + ) + + onTertiaryFixed = DynamicColor.from_palette( + FromPaletteOptions( + name="on_tertiary_fixed", + palette=lambda s: s.tertiary_palette, + tone=lambda s: 100.0 if is_monochrome(s) else 10.0, + background=lambda s: MaterialDynamicColors.tertiaryFixedDim, + second_background=lambda s: MaterialDynamicColors.tertiaryFixed, + contrast_curve=ContrastCurve(4.5, 7, 11, 21), + ) + ) + + onTertiaryFixedVariant = DynamicColor.from_palette( + FromPaletteOptions( + name="on_tertiary_fixed_variant", + palette=lambda s: s.tertiary_palette, + tone=lambda s: 90.0 if is_monochrome(s) else 30.0, + background=lambda s: MaterialDynamicColors.tertiaryFixedDim, + second_background=lambda s: MaterialDynamicColors.tertiaryFixed, + contrast_curve=ContrastCurve(3, 4.5, 7, 11), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/tone_delta_pair.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/tone_delta_pair.py new file mode 100644 index 0000000..112592a --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/dynamiccolor/tone_delta_pair.py @@ -0,0 +1,19 @@ +from typing import Union + +TonePolarity = Union["darker", "lighter", "nearer", "farther"] + + +class ToneDeltaPair: + def __init__( + self, + role_a: None, # DynamicColor, + role_b: None, # DynamicColor, + delta: int, + polarity: TonePolarity, + stay_together: bool, + ): + self.role_a = role_a + self.role_b = role_b + self.delta = delta + self.polarity = polarity + self.stay_together = stay_together diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__init__.py new file mode 100644 index 0000000..ec5a9f8 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__init__.py @@ -0,0 +1 @@ +from .hct import Hct diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..bad60cd Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/cam16.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/cam16.cpython-311.pyc new file mode 100644 index 0000000..5fb462f Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/cam16.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/hct.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/hct.cpython-311.pyc new file mode 100644 index 0000000..e02448e Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/hct.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/hct_solver.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/hct_solver.cpython-311.pyc new file mode 100644 index 0000000..9a82029 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/hct_solver.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/viewing_conditions.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/viewing_conditions.cpython-311.pyc new file mode 100644 index 0000000..3ee1bdf Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/__pycache__/viewing_conditions.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/cam16.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/cam16.py new file mode 100644 index 0000000..e5a3650 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/cam16.py @@ -0,0 +1,325 @@ +import math +from materialyoucolor.utils.math_utils import signum +from materialyoucolor.utils.color_utils import linearized, argb_from_xyz +from materialyoucolor.hct.viewing_conditions import ViewingConditions + + +class Cam16: + def __init__(self, hue, chroma, j, q, m, s, jstar, astar, bstar): + self.hue = hue + self.chroma = chroma + self.j = j + self.q = q + self.m = m + self.s = s + self.jstar = jstar + self.astar = astar + self.bstar = bstar + + def distance(self, other_cam16) -> float: + d_j = self.jstar - other_cam16.jstar + d_a = self.astar - other_cam16.astar + d_b = self.bstar - other_cam16.bstar + d_e_prime = math.sqrt(d_j * d_j + d_a * d_a + d_b * d_b) + return 1.41 * pow(d_e_prime, 0.63) + + @staticmethod + def from_int(argb: int): + return Cam16.from_int_in_viewing_conditions(argb, ViewingConditions.DEFAULT()) + + @staticmethod + def from_int_in_viewing_conditions( + argb: int, viewing_conditions: ViewingConditions + ): + red = (argb & 0x00FF0000) >> 16 + green = (argb & 0x0000FF00) >> 8 + blue = argb & 0x000000FF + red_l = linearized(red) + green_l = linearized(green) + blue_l = linearized(blue) + + x = 0.41233895 * red_l + 0.35762064 * green_l + 0.18051042 * blue_l + y = 0.2126 * red_l + 0.7152 * green_l + 0.0722 * blue_l + z = 0.01932141 * red_l + 0.11916382 * green_l + 0.95034478 * blue_l + + r_c = 0.401288 * x + 0.650173 * y - 0.051461 * z + g_c = -0.250268 * x + 1.204414 * y + 0.045854 * z + b_c = -0.002079 * x + 0.048952 * y + 0.953127 * z + + r_d = viewing_conditions.rgb_d[0] * r_c + g_d = viewing_conditions.rgb_d[1] * g_c + b_d = viewing_conditions.rgb_d[2] * b_c + + r_af = pow((viewing_conditions.fl * abs(r_d)) / 100.0, 0.42) + g_af = pow((viewing_conditions.fl * abs(g_d)) / 100.0, 0.42) + b_af = pow((viewing_conditions.fl * abs(b_d)) / 100.0, 0.42) + + r_a = (signum(r_d) * 400.0 * r_af) / (r_af + 27.13) + g_a = (signum(g_d) * 400.0 * g_af) / (g_af + 27.13) + b_a = (signum(b_d) * 400.0 * b_af) / (b_af + 27.13) + + a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0 + b = (r_a + g_a - 2.0 * b_a) / 9.0 + u = (20.0 * r_a + 20.0 * g_a + 21.0 * b_a) / 20.0 + p2 = (40.0 * r_a + 20.0 * g_a + b_a) / 20.0 + atan2 = math.atan2(b, a) + atan_degrees = (atan2 * 180.0) / math.pi + hue = ( + atan_degrees + 360.0 + if atan_degrees < 0 + else atan_degrees + if atan_degrees >= 360 + else atan_degrees + ) + hue_radians = (hue * math.pi) / 180.0 + + ac = p2 * viewing_conditions.nbb + j = 100.0 * pow( + ac / viewing_conditions.aw, viewing_conditions.c * viewing_conditions.z + ) + q = ( + (4.0 / viewing_conditions.c) + * math.sqrt(j / 100.0) + * (viewing_conditions.aw + 4.0) + * viewing_conditions.f_l_root + ) + hue_prime = hue + 360 if hue < 20.14 else hue + e_hue = 0.25 * (math.cos((hue_prime * math.pi) / 180.0 + 2.0) + 3.8) + p1 = (50000.0 / 13.0) * e_hue * viewing_conditions.nc * viewing_conditions.ncb + t = (p1 * math.sqrt(a * a + b * b)) / (u + 0.305) + alpha = pow(t, 0.9) * pow(1.64 - pow(0.29, viewing_conditions.n), 0.73) + c = alpha * math.sqrt(j / 100.0) + m = c * viewing_conditions.f_l_root + s = 50.0 * math.sqrt( + (alpha * viewing_conditions.c) / (viewing_conditions.aw + 4.0) + ) + j_star = ((1.0 + 100.0 * 0.007) * j) / (1.0 + 0.007 * j) + m_star = (1.0 / 0.0228) * math.log(1.0 + 0.0228 * m) + a_star = m_star * math.cos(hue_radians) + b_star = m_star * math.sin(hue_radians) + + return Cam16(hue, c, j, q, m, s, j_star, a_star, b_star) + + @staticmethod + def from_jch(j: float, c: float, h: float): + return Cam16.from_jch_in_viewing_conditions( + j, c, h, ViewingConditions.DEFAULT() + ) + + @staticmethod + def from_jch_in_viewing_conditions( + j: float, c: float, h: float, viewing_conditions: ViewingConditions + ): + q = ( + (4.0 / viewing_conditions.c) + * math.sqrt(j / 100.0) + * (viewing_conditions.aw + 4.0) + * viewing_conditions.f_l_root + ) + m = c * viewing_conditions.f_l_root + alpha = c / math.sqrt(j / 100.0) + s = 50.0 * math.sqrt( + (alpha * viewing_conditions.c) / (viewing_conditions.aw + 4.0) + ) + hue_radians = (h * math.pi) / 180.0 + j_star = ((1.0 + 100.0 * 0.007) * j) / (1.0 + 0.007 * j) + m_star = (1.0 / 0.0228) * math.log(1.0 + 0.0228 * m) + a_star = m_star * math.cos(hue_radians) + b_star = m_star * math.sin(hue_radians) + return Cam16(h, c, j, q, m, s, j_star, a_star, b_star) + + @staticmethod + def from_ucs(jstar: float, astar: float, bstar: float): + return Cam16.from_ucs_in_viewing_conditions( + jstar, astar, bstar, ViewingConditions.DEFAULT() + ) + + @staticmethod + def from_ucs_in_viewing_conditions( + jstar: float, astar: float, bstar: float, viewing_conditions: ViewingConditions + ): + a = astar + b = bstar + m = math.sqrt(a * a + b * b) + M = (math.exp(m * 0.0228) - 1.0) / 0.0228 + c = M / viewing_conditions.f_l_root + h = math.atan2(b, a) * (180.0 / math.pi) + if h < 0.0: + h += 360.0 + j = jstar / (1 - (jstar - 100) * 0.007) + return Cam16.from_jch_in_viewing_conditions(j, c, h, viewing_conditions) + + def to_int(self) -> int: + return self.viewed(ViewingConditions.DEFAULT()) + + def viewed(self, viewing_conditions: ViewingConditions) -> int: + alpha = ( + 0.0 + if self.chroma == 0.0 or self.j == 0.0 + else self.chroma / math.sqrt(self.j / 100.0) + ) + + t = pow(alpha / pow(1.64 - pow(0.29, viewing_conditions.n), 0.73), 1.0 / 0.9) + hRad = (self.hue * math.pi) / 180.0 + + eHue = 0.25 * (math.cos(hRad + 2.0) + 3.8) + ac = viewing_conditions.aw * pow( + self.j / 100.0, 1.0 / viewing_conditions.c / viewing_conditions.z + ) + p1 = eHue * (50000.0 / 13.0) * viewing_conditions.nc * viewing_conditions.ncb + p2 = ac / viewing_conditions.nbb + + hSin = math.sin(hRad) + hCos = math.cos(hRad) + + gamma = (23.0 * (p2 + 0.305) * t) / ( + 23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin + ) + a = gamma * hCos + b = gamma * hSin + rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0 + gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0 + bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0 + + rCBase = max(0, (27.13 * abs(rA)) / (400.0 - abs(rA))) + rC = signum(rA) * (100.0 / viewing_conditions.fl) * pow(rCBase, 1.0 / 0.42) + gCBase = max(0, (27.13 * abs(gA)) / (400.0 - abs(gA))) + gC = signum(gA) * (100.0 / viewing_conditions.fl) * pow(gCBase, 1.0 / 0.42) + bCBase = max(0, (27.13 * abs(bA)) / (400.0 - abs(bA))) + bC = signum(bA) * (100.0 / viewing_conditions.fl) * pow(bCBase, 1.0 / 0.42) + rF = rC / viewing_conditions.rgb_d[0] + gF = gC / viewing_conditions.rgb_d[1] + bF = bC / viewing_conditions.rgb_d[2] + + x = 1.86206786 * rF - 1.01125463 * gF + 0.14918677 * bF + y = 0.38752654 * rF + 0.62144744 * gF - 0.00897398 * bF + z = -0.01584150 * rF - 0.03412294 * gF + 1.04996444 * bF + + return argb_from_xyz(x, y, z) + + @staticmethod + def from_xyz_in_viewing_conditions( + x: float, y: float, z: float, viewing_conditions: ViewingConditions + ): + r_c = 0.401288 * x + 0.650173 * y - 0.051461 * z + g_c = -0.250268 * x + 1.204414 * y + 0.045854 * z + b_c = -0.002079 * x + 0.048952 * y + 0.953127 * z + + r_d = viewing_conditions.rgb_d[0] * r_c + g_d = viewing_conditions.rgb_d[1] * g_c + b_d = viewing_conditions.rgb_d[2] * b_c + + r_af = pow(viewing_conditions.fl * abs(r_d) / 100.0, 0.42) + g_af = pow(viewing_conditions.fl * abs(g_d) / 100.0, 0.42) + b_af = pow(viewing_conditions.fl * abs(b_d) / 100.0, 0.42) + r_a = signum(r_d) * 400.0 * r_af / (r_af + 27.13) + g_a = signum(g_d) * 400.0 * g_af / (g_af + 27.13) + b_a = signum(b_d) * 400.0 * b_af / (b_af + 27.13) + + a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0 + b = (r_a + g_a - 2.0 * b_a) / 9.0 + + u = (20.0 * r_a + 20.0 * g_a + 21.0 * b_a) / 20.0 + p2 = (40.0 * r_a + 20.0 * g_a + b_a) / 20.0 + + atan2 = math.atan2(b, a) + atan_degrees = atan2 * 180.0 / math.pi + hue = ( + atan_degrees + 360.0 + if atan_degrees < 0 + else atan_degrees - 360.0 + if atan_degrees >= 360 + else atan_degrees + ) + hue_radians = hue * math.pi / 180.0 + + ac = p2 * viewing_conditions.nbb + + J = 100.0 * pow( + ac / viewing_conditions.aw, viewing_conditions.c * viewing_conditions.z + ) + Q = ( + (4.0 / viewing_conditions.c) + * math.sqrt(J / 100.0) + * (viewing_conditions.aw + 4.0) + * viewing_conditions.f_l_root + ) + + hue_prime = hue + 360.0 if hue < 20.14 else hue + e_hue = (1.0 / 4.0) * (math.cos(hue_prime * math.pi / 180.0 + 2.0) + 3.8) + p1 = 50000.0 / 13.0 * e_hue * viewing_conditions.nc * viewing_conditions.ncb + t = p1 * math.sqrt(a * a + b * b) / (u + 0.305) + alpha = pow(t, 0.9) * pow(1.64 - pow(0.29, viewing_conditions.n), 0.73) + C = alpha * math.sqrt(J / 100.0) + M = C * viewing_conditions.f_l_root + s = 50.0 * math.sqrt( + (alpha * viewing_conditions.c) / (viewing_conditions.aw + 4.0) + ) + j_star = (1.0 + 100.0 * 0.007) * J / (1.0 + 0.007 * J) + m_star = math.log(1.0 + 0.0228 * M) / 0.0228 + a_star = m_star * math.cos(hue_radians) + b_star = m_star * math.sin(hue_radians) + return Cam16(hue, C, J, Q, M, s, j_star, a_star, b_star) + + def xyz_in_viewing_conditions( + self, viewing_conditions: ViewingConditions + ) -> list[float]: + alpha = ( + 0.0 + if (self.chroma == 0.0 or self.j == 0.0) + else self.chroma / math.sqrt(self.j / 100.0) + ) + + t = math.pow( + alpha / math.pow(1.64 - math.pow(0.29, viewing_conditions.n), 0.73), + 1.0 / 0.9, + ) + h_rad = self.hue * math.pi / 180.0 + + e_hue = 0.25 * (math.cos(h_rad + 2.0) + 3.8) + ac = viewing_conditions.aw * math.pow( + self.j / 100.0, 1.0 / viewing_conditions.c / viewing_conditions.z + ) + p1 = e_hue * (50000.0 / 13.0) * viewing_conditions.nc * viewing_conditions.ncb + + p2 = ac / viewing_conditions.nbb + + h_sin = math.sin(h_rad) + h_cos = math.cos(h_rad) + + gamma = (23.0 * (p2 + 0.305) * t) / ( + 23.0 * p1 + 11 * t * h_cos + 108.0 * t * h_sin + ) + a = gamma * h_cos + b = gamma * h_sin + r_a = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0 + g_a = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0 + b_a = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0 + + r_c_base = max(0, (27.13 * abs(r_a)) / (400.0 - abs(r_a))) + r_c = ( + signum(r_a) + * (100.0 / viewing_conditions.fl) + * math.pow(r_c_base, 1.0 / 0.42) + ) + g_c_base = max(0, (27.13 * abs(g_a)) / (400.0 - abs(g_a))) + g_c = ( + signum(g_a) + * (100.0 / viewing_conditions.fl) + * math.pow(g_c_base, 1.0 / 0.42) + ) + b_c_base = max(0, (27.13 * abs(b_a)) / (400.0 - abs(b_a))) + b_c = ( + signum(b_a) + * (100.0 / viewing_conditions.fl) + * math.pow(b_c_base, 1.0 / 0.42) + ) + r_f = r_c / viewing_conditions.rgb_d[0] + g_f = g_c / viewing_conditions.rgb_d[1] + b_f = b_c / viewing_conditions.rgb_d[2] + + x = 1.86206786 * r_f - 1.01125463 * g_f + 0.14918677 * b_f + y = 0.38752654 * r_f + 0.62144744 * g_f - 0.00897398 * b_f + z = -0.01584150 * r_f - 0.03412294 * g_f + 1.04996444 * b_f + + return [x, y, z] diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/hct.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/hct.py new file mode 100644 index 0000000..91218f2 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/hct.py @@ -0,0 +1,84 @@ +from materialyoucolor.utils.color_utils import lstar_from_argb, lstar_from_y +from materialyoucolor.hct.viewing_conditions import ViewingConditions +from materialyoucolor.hct.cam16 import Cam16 +from materialyoucolor.hct.hct_solver import HctSolver +from materialyoucolor.utils.color_utils import rgba_from_argb + + +class Hct: + def __init__(self, argb: int): + cam = Cam16.from_int(argb) + self.internal_hue = cam.hue + self.internal_chroma = cam.chroma + self.internal_tone = lstar_from_argb(argb) + self.argb = argb + + def set_internal_state(self, argb: int): + cam = Cam16.from_int(argb) + self.internal_hue = cam.hue + self.internal_chroma = cam.chroma + self.internal_tone = lstar_from_argb(argb) + self.argb = argb + + @staticmethod + def from_hct(hue: float, chroma: float, tone: float): + return Hct(int(HctSolver.solve_to_int(hue, chroma, tone))) + + @staticmethod + def from_int(argb: int): + return Hct(argb) + + def to_int(self) -> int: + return self.argb + + def to_rgba(self) -> list: + return rgba_from_argb(self.argb) + + @property + def hue(self) -> float: + return self.internal_hue + + @hue.setter + def hue(self, new_hue: float): + self.set_internal_state( + int( + HctSolver.solve_to_int( + new_hue, self.internal_chroma, self.internal_tone + ) + ) + ) + + @property + def chroma(self) -> float: + return self.internal_chroma + + @chroma.setter + def chroma(self, new_chroma: float): + self.set_internal_state( + HctSolver.solve_to_int(self.internal_hue, new_chroma, self.internal_tone) + ) + + @property + def tone(self) -> float: + return self.internal_tone + + @tone.setter + def tone(self, new_tone: float): + self.set_internal_state( + int( + HctSolver.solve_to_int( + self.internal_hue, self.internal_chroma, new_tone + ) + ) + ) + + def in_viewing_conditions(self, vc: ViewingConditions): + cam = Cam16.from_int(self.to_int()) + viewed_in_vc = cam.xyz_in_viewing_conditions(vc) + recast_in_vc = Cam16.from_xyz_in_viewing_conditions( + viewed_in_vc[0], viewed_in_vc[1], viewed_in_vc[2], ViewingConditions.make() + ) + recast_hct = Hct.from_hct( + recast_in_vc.hue, recast_in_vc.chroma, lstar_from_y(viewed_in_vc[1]) + ) + return recast_hct diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/hct_solver.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/hct_solver.py new file mode 100644 index 0000000..0311235 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/hct_solver.py @@ -0,0 +1,591 @@ +import math + +from materialyoucolor.utils.math_utils import ( + signum, + matrix_multiply, + sanitize_degrees_double, +) +from materialyoucolor.utils.color_utils import ( + argb_from_lstar, + argb_from_linrgb, + y_from_lstar, +) +from materialyoucolor.hct.cam16 import Cam16 +from materialyoucolor.hct.viewing_conditions import ViewingConditions + + +class HctSolver: + SCALED_DISCOUNT_FROM_LINRGB = [ + [ + 0.001200833568784504, + 0.002389694492170889, + 0.0002795742885861124, + ], + [ + 0.0005891086651375999, + 0.0029785502573438758, + 0.0003270666104008398, + ], + [ + 0.00010146692491640572, + 0.0005364214359186694, + 0.0032979401770712076, + ], + ] + + LINRGB_FROM_SCALED_DISCOUNT = [ + [ + 1373.2198709594231, + -1100.4251190754821, + -7.278681089101213, + ], + [ + -271.815969077903, + 559.6580465940733, + -32.46047482791194, + ], + [ + 1.9622899599665666, + -57.173814538844006, + 308.7233197812385, + ], + ] + + Y_FROM_LINRGB = [0.2126, 0.7152, 0.0722] + + CRITICAL_PLANES = [ + 0.015176349177441876, + 0.045529047532325624, + 0.07588174588720938, + 0.10623444424209313, + 0.13658714259697685, + 0.16693984095186062, + 0.19729253930674434, + 0.2276452376616281, + 0.2579979360165119, + 0.28835063437139563, + 0.3188300904430532, + 0.350925934958123, + 0.3848314933096426, + 0.42057480301049466, + 0.458183274052838, + 0.4976837250274023, + 0.5391024159806381, + 0.5824650784040898, + 0.6277969426914107, + 0.6751227633498623, + 0.7244668422128921, + 0.775853049866786, + 0.829304845476233, + 0.8848452951698498, + 0.942497089126609, + 1.0022825574869039, + 1.0642236851973577, + 1.1283421258858297, + 1.1946592148522128, + 1.2631959812511864, + 1.3339731595349034, + 1.407011200216447, + 1.4823302800086415, + 1.5599503113873272, + 1.6398909516233677, + 1.7221716113234105, + 1.8068114625156377, + 1.8938294463134073, + 1.9832442801866852, + 2.075074464868551, + 2.1693382909216234, + 2.2660538449872063, + 2.36523901573795, + 2.4669114995532007, + 2.5710888059345764, + 2.6777882626779785, + 2.7870270208169257, + 2.898822059350997, + 3.0131901897720907, + 3.1301480604002863, + 3.2497121605402226, + 3.3718988244681087, + 3.4967242352587946, + 3.624204428461639, + 3.754355295633311, + 3.887192587735158, + 4.022731918402185, + 4.160988767090289, + 4.301978482107941, + 4.445716283538092, + 4.592217266055746, + 4.741496401646282, + 4.893568542229298, + 5.048448422192488, + 5.20615066083972, + 5.3666897647573375, + 5.5300801301023865, + 5.696336044816294, + 5.865471690767354, + 6.037501145825082, + 6.212438385869475, + 6.390297286737924, + 6.571091626112461, + 6.7548350853498045, + 6.941541251256611, + 7.131223617812143, + 7.323895587840543, + 7.5195704746346665, + 7.7182615035334345, + 7.919981813454504, + 8.124744458384042, + 8.332562408825165, + 8.543448553206703, + 8.757415699253682, + 8.974476575321063, + 9.194643831691977, + 9.417930041841839, + 9.644347703669503, + 9.873909240696694, + 10.106627003236781, + 10.342513269534024, + 10.58158024687427, + 10.8238400726681, + 11.069304815507364, + 11.317986476196008, + 11.569896988756009, + 11.825048221409341, + 12.083451977536606, + 12.345119996613247, + 12.610063955123938, + 12.878295467455942, + 13.149826086772048, + 13.42466730586372, + 13.702830557985108, + 13.984327217668513, + 14.269168601521828, + 14.55736596900856, + 14.848930523210871, + 15.143873411576273, + 15.44220572664832, + 15.743938506781891, + 16.04908273684337, + 16.35764934889634, + 16.66964922287304, + 16.985093187232053, + 17.30399201960269, + 17.62635644741625, + 17.95219714852476, + 18.281524751807332, + 18.614349837764564, + 18.95068293910138, + 19.290534541298456, + 19.633915083172692, + 19.98083495742689, + 20.331304511189067, + 20.685334046541502, + 21.042933821039977, + 21.404114048223256, + 21.76888489811322, + 22.137256497705877, + 22.50923893145328, + 22.884842241736916, + 23.264076429332462, + 23.6469514538663, + 24.033477234264016, + 24.42366364919083, + 24.817520537484558, + 25.21505769858089, + 25.61628489293138, + 26.021211842414342, + 26.429848230738664, + 26.842203703840827, + 27.258287870275353, + 27.678110301598522, + 28.10168053274597, + 28.529008062403893, + 28.96010235337422, + 29.39497283293396, + 29.83362889318845, + 30.276079891419332, + 30.722335150426627, + 31.172403958865512, + 31.62629557157785, + 32.08401920991837, + 32.54558406207592, + 33.010999283389665, + 33.4802739966603, + 33.953417292456834, + 34.430438229418264, + 34.911345834551085, + 35.39614910352207, + 35.88485700094671, + 36.37747846067349, + 36.87402238606382, + 37.37449765026789, + 37.87891309649659, + 38.38727753828926, + 38.89959975977785, + 39.41588851594697, + 39.93615253289054, + 40.460400508064545, + 40.98864111053629, + 41.520882981230194, + 42.05713473317016, + 42.597404951718396, + 43.141702194811224, + 43.6900349931913, + 44.24241185063697, + 44.798841244188324, + 45.35933162437017, + 45.92389141541209, + 46.49252901546552, + 47.065252796817916, + 47.64207110610409, + 48.22299226451468, + 48.808024568002054, + 49.3971762874833, + 49.9904556690408, + 50.587870934119984, + 51.189430279724725, + 51.79514187861014, + 52.40501387947288, + 53.0190544071392, + 53.637271562750364, + 54.259673423945976, + 54.88626804504493, + 55.517063457223934, + 56.15206766869424, + 56.79128866487574, + 57.43473440856916, + 58.08241284012621, + 58.734331877617365, + 59.39049941699807, + 60.05092333227251, + 60.715611475655585, + 61.38457167773311, + 62.057811747619894, + 62.7353394731159, + 63.417162620860914, + 64.10328893648692, + 64.79372614476921, + 65.48848194977529, + 66.18756403501224, + 66.89098006357258, + 67.59873767827808, + 68.31084450182222, + 69.02730813691093, + 69.74813616640164, + 70.47333615344107, + 71.20291564160104, + 71.93688215501312, + 72.67524319850172, + 73.41800625771542, + 74.16517879925733, + 74.9167682708136, + 75.67278210128072, + 76.43322770089146, + 77.1981124613393, + 77.96744375590167, + 78.74122893956174, + 79.51947534912904, + 80.30219030335869, + 81.08938110306934, + 81.88105503125999, + 82.67721935322541, + 83.4778813166706, + 84.28304815182372, + 85.09272707154808, + 85.90692527145302, + 86.72564993000343, + 87.54890820862819, + 88.3767072518277, + 89.2090541872801, + 90.04595612594655, + 90.88742016217518, + 91.73345337380438, + 92.58406282226491, + 93.43925555268066, + 94.29903859396902, + 95.16341895893969, + 96.03240364439274, + 96.9059996312159, + 97.78421388448044, + 98.6670533535366, + 99.55452497210776, + ] + + @staticmethod + def sanitize_radians(angle: float) -> float: + return (angle + math.pi * 8) % (math.pi * 2) + + @staticmethod + def true_delinearized(rgb_component: float) -> float: + normalized = rgb_component / 100.0 + delinearized = 0.0 + if normalized <= 0.0031308: + delinearized = normalized * 12.92 + else: + delinearized = 1.055 * pow(normalized, 1.0 / 2.4) - 0.055 + return delinearized * 255.0 + + @staticmethod + def chromatic_adaptation(component: float) -> float: + af = pow(abs(component), 0.42) + return signum(component) * 400.0 * af / (af + 27.13) + + @staticmethod + def hue_of(linrgb: list[float]) -> float: + scaled_discount = matrix_multiply(linrgb, HctSolver.SCALED_DISCOUNT_FROM_LINRGB) + r_a = HctSolver.chromatic_adaptation(scaled_discount[0]) + g_a = HctSolver.chromatic_adaptation(scaled_discount[1]) + b_a = HctSolver.chromatic_adaptation(scaled_discount[2]) + a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0 + b = (r_a + g_a - 2.0 * b_a) / 9.0 + return math.atan2(b, a) + + @staticmethod + def are_in_cyclic_order(a: float, b: float, c: float) -> bool: + delta_ab = HctSolver.sanitize_radians(b - a) + delta_ac = HctSolver.sanitize_radians(c - a) + return delta_ab < delta_ac + + @staticmethod + def intercept(source: float, mid: float, target: float) -> float: + return (mid - source) / (target - source) + + @staticmethod + def lerp_point(source: list[float], t: float, target: list[float]) -> list[float]: + return [ + source[0] + (target[0] - source[0]) * t, + source[1] + (target[1] - source[1]) * t, + source[2] + (target[2] - source[2]) * t, + ] + + @staticmethod + def set_coordinate( + source: list[float], coordinate: float, target: list[float], axis: int + ) -> list[float]: + t = HctSolver.intercept(source[axis], coordinate, target[axis]) + return HctSolver.lerp_point(source, t, target) + + @staticmethod + def is_bounded(x: float) -> bool: + return 0.0 <= x <= 100.0 + + @staticmethod + def nth_vertex(y: float, n: float) -> list[float]: + kr = HctSolver.Y_FROM_LINRGB[0] + kg = HctSolver.Y_FROM_LINRGB[1] + kb = HctSolver.Y_FROM_LINRGB[2] + coord_a = 0.0 if n % 4 <= 1 else 100.0 + coord_b = 0.0 if n % 2 == 0 else 100.0 + + if n < 4: + g = coord_a + b = coord_b + r = (y - g * kg - b * kb) / kr + if HctSolver.is_bounded(r): + return [r, g, b] + else: + return [-1.0, -1.0, -1.0] + elif n < 8: + b = coord_a + r = coord_b + g = (y - r * kr - b * kb) / kg + if HctSolver.is_bounded(g): + return [r, g, b] + else: + return [-1.0, -1.0, -1.0] + else: + r = coord_a + g = coord_b + b = (y - r * kr - g * kg) / kb + if HctSolver.is_bounded(b): + return [r, g, b] + else: + return [-1.0, -1.0, -1.0] + + @staticmethod + def bisect_to_segment(y: float, target_hue: float) -> list[list[float]]: + left = [-1.0, -1.0, -1.0] + right = left + left_hue = 0.0 + right_hue = 0.0 + initialized = False + uncut = True + + for n in range(12): + mid = HctSolver.nth_vertex(y, n) + + if mid[0] < 0: + continue + + mid_hue = HctSolver.hue_of(mid) + + if not initialized: + left = mid + right = mid + left_hue = mid_hue + right_hue = mid_hue + initialized = True + continue + + if uncut or HctSolver.are_in_cyclic_order(left_hue, mid_hue, right_hue): + uncut = False + + if HctSolver.are_in_cyclic_order(left_hue, target_hue, mid_hue): + right = mid + right_hue = mid_hue + else: + left = mid + left_hue = mid_hue + + return [left, right] + + @staticmethod + def midpoint(a: list[float], b: list[float]) -> list[float]: + return [ + (a[0] + b[0]) / 2, + (a[1] + b[1]) / 2, + (a[2] + b[2]) / 2, + ] + + @staticmethod + def critical_plane_below(x: float) -> float: + return math.floor(x - 0.5) + + @staticmethod + def critical_plane_above(x: float) -> float: + return math.ceil(x - 0.5) + + @staticmethod + def bisect_to_limit(y: float, target_hue: float) -> list[float]: + segment = HctSolver.bisect_to_segment(y, target_hue) + left = segment[0] + left_hue = HctSolver.hue_of(left) + right = segment[1] + + for axis in range(3): + if left[axis] != right[axis]: + l_plane = -1 + r_plane = 255 + + if left[axis] < right[axis]: + l_plane = HctSolver.critical_plane_below( + HctSolver.true_delinearized(left[axis]) + ) + r_plane = HctSolver.critical_plane_above( + HctSolver.true_delinearized(right[axis]) + ) + else: + l_plane = HctSolver.critical_plane_above( + HctSolver.true_delinearized(left[axis]) + ) + r_plane = HctSolver.critical_plane_below( + HctSolver.true_delinearized(right[axis]) + ) + + for _ in range(8): + if abs(r_plane - l_plane) <= 1: + break + else: + m_plane = math.floor((l_plane + r_plane) / 2.0) + mid_plane_coordinate = HctSolver.CRITICAL_PLANES[m_plane] + mid = HctSolver.set_coordinate( + left, mid_plane_coordinate, right, axis + ) + mid_hue = HctSolver.hue_of(mid) + + if HctSolver.are_in_cyclic_order(left_hue, target_hue, mid_hue): + right = mid + r_plane = m_plane + else: + left = mid + left_hue = mid_hue + l_plane = m_plane + + return HctSolver.midpoint(left, right) + + @staticmethod + def inverse_chromatic_adaptation(adapted: float) -> float: + adapted_abs = abs(adapted) + base = max(0, 27.13 * adapted_abs / (400.0 - adapted_abs)) + return signum(adapted) * math.pow(base, 1.0 / 0.42) + + @staticmethod + def find_result_by_j(hue_radians: float, chroma: float, y: float) -> int: + j = math.sqrt(y) * 11.0 + viewing_conditions = ViewingConditions.DEFAULT() + t_inner_coeff = 1 / math.pow(1.64 - math.pow(0.29, viewing_conditions.n), 0.73) + e_hue = 0.25 * (math.cos(hue_radians + 2.0) + 3.8) + p1 = e_hue * (50000.0 / 13.0) * viewing_conditions.nc * viewing_conditions.ncb + h_sin = math.sin(hue_radians) + h_cos = math.cos(hue_radians) + + for iteration_round in range(5): + j_normalized = j / 100.0 + alpha = ( + chroma / math.sqrt(j_normalized) + if (chroma != 0.0 and j != 0.0) + else 0.0 + ) + t = math.pow(alpha * t_inner_coeff, 1.0 / 0.9) + ac = viewing_conditions.aw * math.pow( + j_normalized, 1.0 / viewing_conditions.c / viewing_conditions.z + ) + p2 = ac / viewing_conditions.nbb + gamma = ( + 23.0 + * (p2 + 0.305) + * t + / (23.0 * p1 + 11 * t * h_cos + 108.0 * t * h_sin) + ) + a = gamma * h_cos + b = gamma * h_sin + r_a = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0 + g_a = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0 + b_a = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0 + r_c_scaled = HctSolver.inverse_chromatic_adaptation(r_a) + g_c_scaled = HctSolver.inverse_chromatic_adaptation(g_a) + b_c_scaled = HctSolver.inverse_chromatic_adaptation(b_a) + linrgb = matrix_multiply( + [r_c_scaled, g_c_scaled, b_c_scaled], + HctSolver.LINRGB_FROM_SCALED_DISCOUNT, + ) + + if linrgb[0] < 0 or linrgb[1] < 0 or linrgb[2] < 0: + return 0 + + kR = HctSolver.Y_FROM_LINRGB[0] + kG = HctSolver.Y_FROM_LINRGB[1] + kB = HctSolver.Y_FROM_LINRGB[2] + fnj = kR * linrgb[0] + kG * linrgb[1] + kB * linrgb[2] + + if fnj <= 0: + return 0 + + if iteration_round == 4 or abs(fnj - y) < 0.002: + if linrgb[0] > 100.01 or linrgb[1] > 100.01 or linrgb[2] > 100.01: + return 0 + return argb_from_linrgb(linrgb) + + j = j - (fnj - y) * j / (2 * fnj) + + return 0 + + @staticmethod + def solve_to_int(hue_degrees: float, chroma: float, lstar: float) -> int: + if chroma < 0.0001 or lstar < 0.0001 or lstar > 99.9999: + return argb_from_lstar(lstar) + + hue_degrees = sanitize_degrees_double(hue_degrees) + hue_radians = hue_degrees / 180 * math.pi + y = y_from_lstar(lstar) + exact_answer = HctSolver.find_result_by_j(hue_radians, chroma, y) + + if exact_answer != 0: + return exact_answer + + linrgb = HctSolver.bisect_to_limit(y, hue_radians) + return argb_from_linrgb(linrgb) + + @staticmethod + def solve_to_cam(hueDegrees: float, chroma: float, lstar: float) -> Cam16: + return Cam16.from_int(int(HctSolver.solve_to_int(hueDegrees, chroma, lstar))) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/viewing_conditions.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/viewing_conditions.py new file mode 100644 index 0000000..8effc05 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/hct/viewing_conditions.py @@ -0,0 +1,72 @@ +import math +from materialyoucolor.utils.math_utils import lerp +from materialyoucolor.utils.color_utils import white_point_d65, y_from_lstar + + +class ViewingConditions: + def __init__(self, n, aw, nbb, ncb, c, nc, rgb_d, fl, f_l_root, z): + self.n = n + self.aw = aw + self.nbb = nbb + self.ncb = ncb + self.c = c + self.nc = nc + self.rgb_d = rgb_d + self.fl = fl + self.f_l_root = f_l_root + self.z = z + + @staticmethod + def make( + white_point=white_point_d65(), + adapting_luminance=(200.0 / math.pi) * y_from_lstar(50.0) / 100.0, + background_lstar=50.0, + surround=2.0, + discounting_illuminant=False, + ): + xyz = white_point + r_w = xyz[0] * 0.401288 + xyz[1] * 0.650173 + xyz[2] * -0.051461 + g_w = xyz[0] * -0.250268 + xyz[1] * 1.204414 + xyz[2] * 0.045854 + b_w = xyz[0] * -0.002079 + xyz[1] * 0.048952 + xyz[2] * 0.953127 + f = 0.8 + surround / 10.0 + c = ( + lerp(0.59, 0.69, (f - 0.9) * 10.0) + if f >= 0.9 + else lerp(0.525, 0.59, (f - 0.8) * 10.0) + ) + d = ( + 1.0 + if discounting_illuminant + else f * (1.0 - (1.0 / 3.6) * math.exp((-adapting_luminance - 42.0) / 92.0)) + ) + d = 1.0 if d > 1.0 else 0.0 if d < 0.0 else d + nc = f + rgb_d = [ + d * (100.0 / r_w) + 1.0 - d, + d * (100.0 / g_w) + 1.0 - d, + d * (100.0 / b_w) + 1.0 - d, + ] + k = 1.0 / (5.0 * adapting_luminance + 1.0) + k4 = k**4 + k4_f = 1.0 - k4 + fl = k4 * adapting_luminance + 0.1 * k4_f * k4_f * ( + (5.0 * adapting_luminance) ** (1 / 3) + ) + n = y_from_lstar(background_lstar) / white_point[1] + z = 1.48 + math.sqrt(n) + nbb = 0.725 / pow(n, 0.2) + ncb = nbb + rgb_a_factors = [ + pow((fl * rgb_d[0] * r_w) / 100.0, 0.42), + pow((fl * rgb_d[1] * g_w) / 100.0, 0.42), + pow((fl * rgb_d[2] * b_w) / 100.0, 0.42), + ] + rgb_a = [ + (400.0 * rgb_a_factors[0]) / (rgb_a_factors[0] + 27.13), + (400.0 * rgb_a_factors[1]) / (rgb_a_factors[1] + 27.13), + (400.0 * rgb_a_factors[2]) / (rgb_a_factors[2] + 27.13), + ] + aw = (2.0 * rgb_a[0] + rgb_a[1] + 0.05 * rgb_a[2]) * nbb + return ViewingConditions(n, aw, nbb, ncb, c, nc, rgb_d, fl, pow(fl, 0.25), z) + + DEFAULT = make diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..be79eac Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/core_palette.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/core_palette.cpython-311.pyc new file mode 100644 index 0000000..88b6b70 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/core_palette.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/tonal_palette.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/tonal_palette.cpython-311.pyc new file mode 100644 index 0000000..228b082 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/__pycache__/tonal_palette.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/core_palette.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/core_palette.py new file mode 100644 index 0000000..bd6aa5f --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/core_palette.py @@ -0,0 +1,79 @@ +from materialyoucolor.hct.hct import Hct +from materialyoucolor.palettes.tonal_palette import TonalPalette + + +class CorePaletteColors: + def __init__(self, primary, secondary, tertiary, neutral, neutral_variant, error): + self.primary = primary + self.secondary = secondary + self.tertiary = tertiary + self.neutral = neutral + self.neutral_variant = neutral_variant + self.error = error + + +class CorePalette: + def __init__(self): + self.a1 = None + self.a2 = None + self.a3 = None + self.n1 = None + self.n2 = None + self.error = None + + @staticmethod + def of(argb: int): + return CorePalette._create_core_palette(argb, False) + + @staticmethod + def content_of(argb: int): + return CorePalette._create_core_palette(argb, True) + + @staticmethod + def from_colors(colors: CorePaletteColors): + return CorePalette._create_palette_from_colors(False, colors) + + @staticmethod + def content_from_colors(colors: CorePaletteColors): + return CorePalette._create_palette_from_colors(True, colors) + + @staticmethod + def _create_palette_from_colors(content: bool, colors: CorePaletteColors): + palette = CorePalette() + if colors.secondary: + p = CorePalette._create_core_palette(colors.secondary, content) + palette.a2 = p.a1 + if colors.tertiary: + p = CorePalette._create_core_palette(colors.tertiary, content) + palette.a3 = p.a1 + if colors.error: + p = CorePalette._create_core_palette(colors.error, content) + palette.error = p.a1 + if colors.neutral: + p = CorePalette._create_core_palette(colors.neutral, content) + palette.n1 = p.n1 + if colors.neutral_variant: + p = CorePalette._create_core_palette(colors.neutral_variant, content) + palette.n2 = p.n2 + return palette + + @staticmethod + def _create_core_palette(argb: int, is_content: bool): + hct = Hct.from_int(argb) + hue = hct.hue + chroma = hct.chroma + palette = CorePalette() + if is_content: + palette.a1 = TonalPalette.from_hue_and_chroma(hue, chroma) + palette.a2 = TonalPalette.from_hue_and_chroma(hue, chroma / 3) + palette.a3 = TonalPalette.from_hue_and_chroma(hue + 60, chroma / 2) + palette.n1 = TonalPalette.from_hue_and_chroma(hue, min(chroma / 12, 4)) + palette.n2 = TonalPalette.from_hue_and_chroma(hue, min(chroma / 6, 8)) + else: + palette.a1 = TonalPalette.from_hue_and_chroma(hue, max(48, chroma)) + palette.a2 = TonalPalette.from_hue_and_chroma(hue, 16) + palette.a3 = TonalPalette.from_hue_and_chroma(hue + 60, 24) + palette.n1 = TonalPalette.from_hue_and_chroma(hue, 4) + palette.n2 = TonalPalette.from_hue_and_chroma(hue, 8) + palette.error = TonalPalette.from_hue_and_chroma(25, 84) + return palette diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/tonal_palette.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/tonal_palette.py new file mode 100644 index 0000000..c0c1f8f --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/palettes/tonal_palette.py @@ -0,0 +1,58 @@ +from materialyoucolor.hct import Hct +from materialyoucolor.utils.color_utils import rgba_from_argb +from materialyoucolor.utils.color_utils import argb_from_rgb + + +class TonalPalette: + def __init__(self, hue, chroma, key_color): + self.hue = hue + self.chroma = chroma + self.key_color = key_color + self.cache = {} + + @staticmethod + def from_int(argb: int): + hct = Hct.from_int(argb) + return TonalPalette.from_hct(hct) + + @staticmethod + def from_hct(hct: Hct): + return TonalPalette(hct.hue, hct.chroma, hct) + + @staticmethod + def from_hue_and_chroma(hue: float, chroma: float): + return TonalPalette(hue, chroma, TonalPalette.create_key_color(hue, chroma)) + + @staticmethod + def create_key_color(hue: float, chroma: float) -> Hct: + start_tone = 50.0 + smallest_delta_hct = Hct.from_hct(hue, chroma, start_tone) + smallest_delta = abs(smallest_delta_hct.chroma - chroma) + + for delta in range(1, 50): + if round(chroma) == round(smallest_delta_hct.chroma): + return smallest_delta_hct + + hct_add = Hct.from_hct(hue, chroma, start_tone + delta) + hct_add_delta = abs(hct_add.chroma - chroma) + if hct_add_delta < smallest_delta: + smallest_delta = hct_add_delta + smallest_delta_hct = hct_add + + hct_subtract = Hct.from_hct(hue, chroma, start_tone - delta) + hct_subtract_delta = abs(hct_subtract.chroma - chroma) + if hct_subtract_delta < smallest_delta: + smallest_delta = hct_subtract_delta + smallest_delta_hct = hct_subtract + + return smallest_delta_hct + + def tone(self, tone: float) -> int: + argb = self.cache.get(tone) + if argb is None: + argb = Hct.from_hct(self.hue, self.chroma, tone).to_int() + self.cache[tone] = argb + return rgba_from_argb(argb) + + def get_hct(self, tone: float) -> Hct: + return Hct.from_int(argb_from_rgb(*self.tone(tone))) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/__init__.py new file mode 100644 index 0000000..9353068 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/__init__.py @@ -0,0 +1 @@ +from .celebi import QuantizeCelebi, StbLoadImage \ No newline at end of file diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..f5f2722 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/celebi.cpython-311-x86_64-linux-gnu.so b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/celebi.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000..5a1f32c Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/quantize/celebi.cpython-311-x86_64-linux-gnu.so differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__init__.py new file mode 100644 index 0000000..066c681 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__init__.py @@ -0,0 +1 @@ +from .scheme import Scheme diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..c845943 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/dynamic_scheme.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/dynamic_scheme.cpython-311.pyc new file mode 100644 index 0000000..b79c235 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/dynamic_scheme.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme.cpython-311.pyc new file mode 100644 index 0000000..2f0217f Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_android.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_android.cpython-311.pyc new file mode 100644 index 0000000..7cb520b Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_android.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_content.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_content.cpython-311.pyc new file mode 100644 index 0000000..30d39bc Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_content.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_expressive.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_expressive.cpython-311.pyc new file mode 100644 index 0000000..e8ea1c3 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_expressive.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_fidelity.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_fidelity.cpython-311.pyc new file mode 100644 index 0000000..e5f9c16 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_fidelity.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_fruit_salad.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_fruit_salad.cpython-311.pyc new file mode 100644 index 0000000..f1f408f Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_fruit_salad.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_monochrome.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_monochrome.cpython-311.pyc new file mode 100644 index 0000000..872c8e0 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_monochrome.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_neutral.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_neutral.cpython-311.pyc new file mode 100644 index 0000000..22647fc Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_neutral.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_rainbow.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_rainbow.cpython-311.pyc new file mode 100644 index 0000000..7293d80 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_rainbow.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_tonal_spot.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_tonal_spot.cpython-311.pyc new file mode 100644 index 0000000..0711021 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_tonal_spot.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_vibrant.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_vibrant.cpython-311.pyc new file mode 100644 index 0000000..77157fb Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/scheme_vibrant.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/variant.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/variant.cpython-311.pyc new file mode 100644 index 0000000..78dd767 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/__pycache__/variant.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/dynamic_scheme.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/dynamic_scheme.py new file mode 100644 index 0000000..30b098a --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/dynamic_scheme.py @@ -0,0 +1,60 @@ +from materialyoucolor.hct import Hct +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.utils.math_utils import sanitize_degrees_double + + +class DynamicSchemeOptions: + def __init__( + self, + source_color_argb: int, + variant: Variant, + contrast_level: int, + is_dark: bool, + primary_palette: TonalPalette, + secondary_palette: TonalPalette, + tertiary_palette: TonalPalette, + neutral_palette: TonalPalette, + neutral_variant_palette: TonalPalette, + ): + self.source_color_argb = source_color_argb + self.variant = variant + self.contrast_level = contrast_level + self.is_dark = is_dark + self.primary_palette = primary_palette + self.secondary_palette = secondary_palette + self.tertiary_palette = tertiary_palette + self.neutral_palette = neutral_palette + self.neutral_variant_palette = neutral_variant_palette + + +class DynamicScheme: + def __init__(self, args: DynamicSchemeOptions): + self.source_color_argb = args.source_color_argb + self.variant = args.variant + self.contrast_level = args.contrast_level + self.is_dark = args.is_dark + self.source_color_hct = Hct.from_int(args.source_color_argb) + self.primary_palette = args.primary_palette + self.secondary_palette = args.secondary_palette + self.tertiary_palette = args.tertiary_palette + self.neutral_palette = args.neutral_palette + self.neutral_variant_palette = args.neutral_variant_palette + self.error_palette = TonalPalette.from_hue_and_chroma(25.0, 84.0) + + @staticmethod + def get_rotated_hue(source_color, hues, rotations): + source_hue = source_color.hue + if len(hues) != len(rotations): + raise ValueError( + f"mismatch between hue length {len(hues)} & rotations {len(rotations)}" + ) + if len(rotations) == 1: + return sanitize_degrees_double(source_color.hue + rotations[0]) + size = len(hues) + for i in range(size - 1): + this_hue = hues[i] + next_hue = hues[i + 1] + if this_hue < source_hue < next_hue: + return sanitize_degrees_double(source_hue + rotations[i]) + return source_hue diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme.py new file mode 100644 index 0000000..1f5dd07 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme.py @@ -0,0 +1,106 @@ +from materialyoucolor.palettes.core_palette import CorePalette +from materialyoucolor.utils.color_utils import argb_from_rgba + + +class Scheme: + def __init__(self, props: dict): + self.props = props + [setattr(self, _, self.props[_]) for _ in self.props.keys()] + + @staticmethod + def light_from_rgb(rgb: list[int]): + return Scheme.light_from_core_palette(CorePalette.of(argb_from_rgba(rgb))) + + @staticmethod + def light(argb: int): + return Scheme.light_from_core_palette(CorePalette.of(argb)) + + @staticmethod + def light_content(argb: int): + return Scheme.light_from_core_palette(CorePalette.content_of(argb)) + + @staticmethod + def light_from_core_palette(core: CorePalette): + return Scheme( + { + "primary": core.a1.tone(40), + "onPrimary": core.a1.tone(100), + "primaryContainer": core.a1.tone(90), + "onPrimaryContainer": core.a1.tone(10), + "secondary": core.a2.tone(40), + "onSecondary": core.a2.tone(100), + "secondaryContainer": core.a2.tone(90), + "onSecondaryContainer": core.a2.tone(10), + "tertiary": core.a3.tone(40), + "onTertiary": core.a3.tone(100), + "tertiaryContainer": core.a3.tone(90), + "onTertiaryContainer": core.a3.tone(10), + "error": core.error.tone(40), + "onError": core.error.tone(100), + "errorContainer": core.error.tone(90), + "onErrorContainer": core.error.tone(10), + "background": core.n1.tone( + 98 + ), # Original was 99, but that didn't worked in light shades of yellow like: [4294309340, 4294638290, 4294967264] + "onBackground": core.n1.tone(10), + "surface": core.n1.tone(98), # Here also same + "onSurface": core.n1.tone(10), + "surfaceVariant": core.n2.tone(90), + "onSurfaceVariant": core.n2.tone(30), + "outline": core.n2.tone(50), + "outlineVariant": core.n2.tone(80), + "shadow": core.n1.tone(0), + "scrim": core.n1.tone(0), + "inverseSurface": core.n1.tone(20), + "inverseOnSurface": core.n1.tone(95), + "inversePrimary": core.a1.tone(80), + } + ) + + @staticmethod + def dark_from_rgb(rgb: list[int]): + return Scheme.dark_from_core_palette(CorePalette.of(argb_from_rgba(rgb))) + + @staticmethod + def dark(argb: int): + return Scheme.dark_from_core_palette(CorePalette.of(argb)) + + @staticmethod + def dark_content(argb: int): + return Scheme.dark_from_core_palette(CorePalette.content_of(argb)) + + @staticmethod + def dark_from_core_palette(core: CorePalette): + return Scheme( + { + "primary": core.a1.tone(80), + "onPrimary": core.a1.tone(20), + "primaryContainer": core.a1.tone(30), + "onPrimaryContainer": core.a1.tone(90), + "secondary": core.a2.tone(80), + "onSecondary": core.a2.tone(20), + "secondaryContainer": core.a2.tone(30), + "onSecondaryContainer": core.a2.tone(90), + "tertiary": core.a3.tone(80), + "onTertiary": core.a3.tone(20), + "tertiaryContainer": core.a3.tone(30), + "onTertiaryContainer": core.a3.tone(90), + "error": core.error.tone(80), + "onError": core.error.tone(20), + "errorContainer": core.error.tone(30), + "onErrorContainer": core.error.tone(80), + "background": core.n1.tone(10), + "onBackground": core.n1.tone(90), + "surface": core.n1.tone(10), + "onSurface": core.n1.tone(90), + "surfaceVariant": core.n2.tone(30), + "onSurfaceVariant": core.n2.tone(80), + "outline": core.n2.tone(60), + "outlineVariant": core.n2.tone(30), + "shadow": core.n1.tone(0), + "scrim": core.n1.tone(0), + "inverseSurface": core.n1.tone(90), + "inverseOnSurface": core.n1.tone(20), + "inversePrimary": core.a1.tone(40), + } + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_android.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_android.py new file mode 100644 index 0000000..9b47c86 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_android.py @@ -0,0 +1,98 @@ +from materialyoucolor.palettes.core_palette import CorePalette +from materialyoucolor.utils.color_utils import argb_from_rgba + + +class SchemeAndroid: + def __init__(self, props: dict): + self.props = props + [setattr(self, _, self.props[_]) for _ in self.props.keys()] + + @staticmethod + def light_from_rgb(rgb: list[int]): + return SchemeAndroid.light_from_core_palette( + CorePalette.of(argb_from_rgba(rgb)) + ) + + @staticmethod + def light(argb: int): + return SchemeAndroid.light_from_core_palette(CorePalette.of(argb)) + + @staticmethod + def light_content(argb: int): + return SchemeAndroid.light_from_core_palette(CorePalette.content_of(argb)) + + @staticmethod + def light_from_core_palette(core: CorePalette): + return SchemeAndroid( + { + "colorAccentPrimary": core.a1.tone(90), + "colorAccentPrimaryVariant": core.a1.tone(40), + "colorAccentSecondary": core.a2.tone(90), + "colorAccentSecondaryVariant": core.a2.tone(40), + "colorAccentTertiary": core.a3.tone(90), + "colorAccentTertiaryVariant": core.a3.tone(40), + "textColorPrimary": core.n1.tone(10), + "textColorSecondary": core.n2.tone(30), + "textColorTertiary": core.n2.tone(50), + "textColorPrimaryInverse": core.n1.tone(95), + "textColorSecondaryInverse": core.n1.tone(80), + "textColorTertiaryInverse": core.n1.tone(60), + "colorBackground": core.n1.tone(95), + "colorBackgroundFloating": core.n1.tone(98), + "colorSurface": core.n1.tone(98), + "colorSurfaceVariant": core.n1.tone(90), + "colorSurfaceHighlight": core.n1.tone(100), + "surfaceHeader": core.n1.tone(90), + "underSurface": core.n1.tone(0), + "offState": core.n1.tone(20), + "accentSurface": core.a2.tone(95), + "textPrimaryOnAccent": core.n1.tone(10), + "textSecondaryOnAccent": core.n2.tone(30), + "volumeBackground": core.n1.tone(25), + "scrim": core.n1.tone(80), + } + ) + + @staticmethod + def dark_from_rgb(rgb: list[int]): + return SchemeAndroid.dark_from_core_palette(CorePalette.of(argb_from_rgba(rgb))) + + @staticmethod + def dark(argb: int): + return SchemeAndroid.dark_from_core_palette(CorePalette.of(argb)) + + @staticmethod + def dark_content(argb: int): + return SchemeAndroid.dark_from_core_palette(CorePalette.content_of(argb)) + + @staticmethod + def dark_from_core_palette(core: CorePalette): + return SchemeAndroid( + { + "colorAccentPrimary": core.a1.tone(90), + "colorAccentPrimaryVariant": core.a1.tone(70), + "colorAccentSecondary": core.a2.tone(90), + "colorAccentSecondaryVariant": core.a2.tone(70), + "colorAccentTertiary": core.a3.tone(90), + "colorAccentTertiaryVariant": core.a3.tone(70), + "textColorPrimary": core.n1.tone(95), + "textColorSecondary": core.n2.tone(80), + "textColorTertiary": core.n2.tone(60), + "textColorPrimaryInverse": core.n1.tone(10), + "textColorSecondaryInverse": core.n1.tone(30), + "textColorTertiaryInverse": core.n1.tone(50), + "colorBackground": core.n1.tone(10), + "colorBackgroundFloating": core.n1.tone(10), + "colorSurface": core.n1.tone(20), + "colorSurfaceVariant": core.n1.tone(30), + "colorSurfaceHighlight": core.n1.tone(35), + "surfaceHeader": core.n1.tone(30), + "underSurface": core.n1.tone(0), + "offState": core.n1.tone(20), + "accentSurface": core.a2.tone(95), + "textPrimaryOnAccent": core.n1.tone(10), + "textSecondaryOnAccent": core.n2.tone(30), + "volumeBackground": core.n1.tone(25), + "scrim": core.n1.tone(80), + } + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_content.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_content.py new file mode 100644 index 0000000..4fdc23f --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_content.py @@ -0,0 +1,35 @@ +from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer +from materialyoucolor.temperature.temperature_cache import TemperatureCache +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette + + +class SchemeContent(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.CONTENT, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, source_color_hct.chroma + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, + max(source_color_hct.chroma - 32.0, source_color_hct.chroma * 0.5), + ), + tertiary_palette=TonalPalette.from_int( + DislikeAnalyzer.fix_if_disliked( + TemperatureCache(source_color_hct).analogous(3, 6)[2] + ).to_int() + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, source_color_hct.chroma / 8.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, source_color_hct.chroma / 8.0 + 4.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_expressive.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_expressive.py new file mode 100644 index 0000000..17a033e --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_expressive.py @@ -0,0 +1,45 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.utils.math_utils import sanitize_degrees_double + + +class SchemeExpressive(DynamicScheme): + hues = [0.0, 21.0, 51.0, 121.0, 151.0, 191.0, 271.0, 321.0, 360.0] + secondary_rotations = [45.0, 95.0, 45.0, 20.0, 45.0, 90.0, 45.0, 45.0, 45.0] + tertiary_rotations = [120.0, 120.0, 20.0, 45.0, 20.0, 15.0, 20.0, 120.0, 120.0] + + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.EXPRESSIVE, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + sanitize_degrees_double(source_color_hct.hue + 240.0), 40.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + DynamicScheme.get_rotated_hue( + source_color_hct, + SchemeExpressive.hues, + SchemeExpressive.secondary_rotations, + ), + 24.0, + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + DynamicScheme.get_rotated_hue( + source_color_hct, + SchemeExpressive.hues, + SchemeExpressive.tertiary_rotations, + ), + 32.0, + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue + 15, 8.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue + 15, 12.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_fidelity.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_fidelity.py new file mode 100644 index 0000000..cd06896 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_fidelity.py @@ -0,0 +1,35 @@ +from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer +from materialyoucolor.temperature.temperature_cache import TemperatureCache +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette + + +class SchemeFidelity(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.FIDELITY, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, source_color_hct.chroma + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, + max(source_color_hct.chroma - 32.0, source_color_hct.chroma * 0.5), + ), + tertiary_palette=TonalPalette.from_int( + DislikeAnalyzer.fix_if_disliked( + TemperatureCache(source_color_hct).complement() + ).to_int() + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, source_color_hct.chroma / 8.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, source_color_hct.chroma / 8.0 + 4.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_fruit_salad.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_fruit_salad.py new file mode 100644 index 0000000..2cd4742 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_fruit_salad.py @@ -0,0 +1,31 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.utils.math_utils import sanitize_degrees_double + + +class SchemeFruitSalad(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.FRUIT_SALAD, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + sanitize_degrees_double(source_color_hct.hue - 50.0), 48.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + sanitize_degrees_double(source_color_hct.hue - 50.0), 36.0 + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 36.0 + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 10.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 16.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_monochrome.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_monochrome.py new file mode 100644 index 0000000..b825c2f --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_monochrome.py @@ -0,0 +1,30 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette + + +class SchemeMonochrome(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.MONOCHROME, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_neutral.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_neutral.py new file mode 100644 index 0000000..81fa2df --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_neutral.py @@ -0,0 +1,30 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette + + +class SchemeNeutral(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.SPRITZ, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 12.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 8.0 + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 16.0 + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 2.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 2.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_rainbow.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_rainbow.py new file mode 100644 index 0000000..5832076 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_rainbow.py @@ -0,0 +1,31 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.utils.math_utils import sanitize_degrees_double + + +class SchemeRainbow(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.RAINBOW, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 48.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 16.0 + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + sanitize_degrees_double(source_color_hct.hue + 60.0), 24.0 + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 0.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_tonal_spot.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_tonal_spot.py new file mode 100644 index 0000000..c420fa9 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_tonal_spot.py @@ -0,0 +1,32 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.utils.math_utils import sanitize_degrees_double + + +class SchemeTonalSpot(DynamicScheme): + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.TONAL_SPOT, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 36.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 16.0 + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + sanitize_degrees_double(source_color_hct.hue + 60.0), + 24.0, + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 6.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 8.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_vibrant.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_vibrant.py new file mode 100644 index 0000000..18e8a05 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/scheme_vibrant.py @@ -0,0 +1,44 @@ +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.palettes.tonal_palette import TonalPalette + + +class SchemeVibrant(DynamicScheme): + hues = [0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0] + secondary_rotations = [18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0] + tertiary_rotations = [35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0] + + def __init__(self, source_color_hct, is_dark, contrast_level): + super().__init__( + DynamicSchemeOptions( + source_color_argb=source_color_hct.to_int(), + variant=Variant.VIBRANT, + contrast_level=contrast_level, + is_dark=is_dark, + primary_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 200.0 + ), + secondary_palette=TonalPalette.from_hue_and_chroma( + DynamicScheme.get_rotated_hue( + source_color_hct, + SchemeVibrant.hues, + SchemeVibrant.secondary_rotations, + ), + 24.0, + ), + tertiary_palette=TonalPalette.from_hue_and_chroma( + DynamicScheme.get_rotated_hue( + source_color_hct, + SchemeVibrant.hues, + SchemeVibrant.tertiary_rotations, + ), + 32.0, + ), + neutral_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 10.0 + ), + neutral_variant_palette=TonalPalette.from_hue_and_chroma( + source_color_hct.hue, 12.0 + ), + ) + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/variant.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/variant.py new file mode 100644 index 0000000..405b2cb --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/scheme/variant.py @@ -0,0 +1,10 @@ +class Variant: + MONOCHROME = "MONOCHROME" + SPRITZ = "SPRITZ" + TONAL_SPOT = "TONAL_SPOT" + VIBRANT = "VIBRANT" + EXPRESSIVE = "EXPRESSIVE" + FIDELITY = "FIDELITY" + CONTENT = "CONTENT" + RAINBOW = "RAINBOW" + FRUIT_SALAD = "FRUIT_SALAD" diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..f1a6c76 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__pycache__/score.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__pycache__/score.cpython-311.pyc new file mode 100644 index 0000000..74e6aa9 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/__pycache__/score.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/score.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/score.py new file mode 100644 index 0000000..7e4eea1 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/score/score.py @@ -0,0 +1,117 @@ +from materialyoucolor.hct import Hct +from materialyoucolor.utils.math_utils import sanitize_degrees_int, difference_degrees +from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer + + +class ScoreOptions: + def __init__( + self, + desired: int, + fallback_color_argb: int, + filter: bool, + dislike_filter: False, + ): + self.desired = desired + self.fallback_color_argb = fallback_color_argb + self.filter = filter + self.dislike_filter = dislike_filter + + +SCORE_OPTION_DEFAULTS = ScoreOptions( + desired=4, + fallback_color_argb=0xFF4285F4, # Google Blue. + filter=True, # Avoid unsuitable colors. + dislike_filter=False, # Fix globally disliked colors +) + + +class Score: + TARGET_CHROMA = 48.0 + WEIGHT_PROPORTION = 0.7 + WEIGHT_CHROMA_ABOVE = 0.3 + WEIGHT_CHROMA_BELOW = 0.1 + CUTOFF_CHROMA = 5.0 + CUTOFF_EXCITED_PROPORTION = 0.01 + + def __init__(self): + pass + + @staticmethod + def score(colors_to_population: dict, options: ScoreOptions = None) -> list[int]: + if options is None: + options = SCORE_OPTION_DEFAULTS + + desired = options.desired + fallback_color_argb = options.fallback_color_argb + filter_enabled = options.filter + dislike_filter = options.dislike_filter + + colors_hct = [] + hue_population = [0] * 360 + population_sum = 0 + + for rgb, population in colors_to_population.items(): + hct = Hct.from_int(rgb) + colors_hct.append(hct) + hue = int(hct.hue) + hue_population[hue] += population + population_sum += population + + hue_excited_proportions = [0.0] * 360 + + for hue in range(360): + proportion = hue_population[hue] / population_sum + for i in range(hue - 14, hue + 16): + neighbor_hue = int(sanitize_degrees_int(i)) + hue_excited_proportions[neighbor_hue] += proportion + + scored_hct = [] + for hct in colors_hct: + hue = int(sanitize_degrees_int(round(hct.hue))) + proportion = hue_excited_proportions[hue] + + if filter_enabled and ( + hct.chroma < Score.CUTOFF_CHROMA + or proportion <= Score.CUTOFF_EXCITED_PROPORTION + ): + continue + + proportion_score = proportion * 100.0 * Score.WEIGHT_PROPORTION + chroma_weight = ( + Score.WEIGHT_CHROMA_BELOW + if hct.chroma < Score.TARGET_CHROMA + else Score.WEIGHT_CHROMA_ABOVE + ) + chroma_score = (hct.chroma - Score.TARGET_CHROMA) * chroma_weight + score = proportion_score + chroma_score + scored_hct.append({"hct": hct, "score": score}) + + scored_hct.sort(key=lambda x: x["score"] , reverse=True) + + chosen_colors = [] + for difference_degrees_ in range(90, 14, -1): + chosen_colors.clear() + for hct in [item["hct"] for item in scored_hct]: + duplicate_hue = any( + difference_degrees(hct.hue, chosen_hct.hue) < difference_degrees_ + for chosen_hct in chosen_colors + ) + if not duplicate_hue: + chosen_colors.append(hct) + if len(chosen_colors) >= desired: + break + if len(chosen_colors) >= desired: + break + + colors = [] + if not chosen_colors: + colors.append(fallback_color_argb) + + if dislike_filter: + for chosen_hct in chosen_colors: + chosen_colors[ + chosen_colors.index(chosen_hct) + ] = DislikeAnalyzer.fix_if_disliked(chosen_hct) + for chosen_hct in chosen_colors: + colors.append(chosen_hct.to_int()) + return colors diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..e8eaf3c Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__pycache__/temperature_cache.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__pycache__/temperature_cache.cpython-311.pyc new file mode 100644 index 0000000..f601ece Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/__pycache__/temperature_cache.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/temperature_cache.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/temperature_cache.py new file mode 100644 index 0000000..fc7b5ac --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/temperature/temperature_cache.py @@ -0,0 +1,203 @@ +import math +from materialyoucolor.hct import Hct +from materialyoucolor.utils.math_utils import ( + sanitize_degrees_int, + sanitize_degrees_double, +) +from materialyoucolor.utils.color_utils import lab_from_argb + + +class TemperatureCache: + def __init__(self, input_hct: Hct): + self.input_hct = input_hct + self.hcts_by_temp_cache = [] + self.hcts_by_hue_cache = [] + self.temps_by_hct_cache = {} + self.input_hct_relative_temperature_cache = -1.0 + self.complement_cache = None + + @property + def hcts_by_temp(self): + if self.hcts_by_temp_cache: + return self.hcts_by_temp_cache + + hcts = self.hcts_by_hue + [self.input_hct] + temperatures_by_hct = self.temps_by_hct + hcts.sort(key=lambda x: temperatures_by_hct[x]) + self.hcts_by_temp_cache = hcts + return hcts + + @property + def warmest(self) -> Hct: + return self.hcts_by_temp[-1] + + @property + def coldest(self) -> Hct: + return self.hcts_by_temp[0] + + def analogous(self, count: int, divisions: int) -> list: + start_hue = round(self.input_hct.hue) + start_hct = self.hcts_by_hue[start_hue] + last_temp = self.relative_temperature(start_hct) + all_colors = [start_hct] + + absolute_total_temp_delta = 0.0 + for i in range(360): + hue = int(sanitize_degrees_int(start_hue + i)) + hct = self.hcts_by_hue[hue] + temp = self.relative_temperature(hct) + temp_delta = abs(temp - last_temp) + last_temp = temp + absolute_total_temp_delta += temp_delta + + hue_addend = 1 + temp_step = absolute_total_temp_delta / divisions + total_temp_delta = 0.0 + last_temp = self.relative_temperature(start_hct) + + while len(all_colors) < divisions: + hue = int(sanitize_degrees_int(start_hue + hue_addend)) + hct = self.hcts_by_hue[hue] + temp = self.relative_temperature(hct) + temp_delta = abs(temp - last_temp) + total_temp_delta += temp_delta + + desired_total_temp_delta_for_index = len(all_colors) * temp_step + index_satisfied = total_temp_delta >= desired_total_temp_delta_for_index + index_addend = 1 + + while index_satisfied and len(all_colors) < divisions: + all_colors.append(hct) + desired_total_temp_delta_for_index = ( + len(all_colors) + index_addend + ) * temp_step + index_satisfied = total_temp_delta >= desired_total_temp_delta_for_index + index_addend += 1 + + last_temp = temp + hue_addend += 1 + + if hue_addend > 360: + while len(all_colors) < divisions: + all_colors.append(hct) + break + + answers = [self.input_hct] + + increase_hue_count = int((count - 1) / 2.0) + for i in range(1, increase_hue_count + 1): + index = 0 - i + while index < 0: + index = len(all_colors) + index + if index >= len(all_colors): + index = index % len(all_colors) + answers.insert(0, all_colors[index]) + + decrease_hue_count = count - increase_hue_count - 1 + for i in range(1, decrease_hue_count + 1): + index = i + while index < 0: + index = len(all_colors) + index + if index >= len(all_colors): + index = index % len(all_colors) + answers.append(all_colors[index]) + + return answers + + def complement(self) -> Hct: + if self.complement_cache is not None: + return self.complement_cache + + coldest_hue = self.coldest.hue + coldest_temp = self.temps_by_hct[self.coldest] + + warmest_hue = self.warmest.hue + warmest_temp = self.temps_by_hct[self.warmest] + range_temp = warmest_temp - coldest_temp + start_hue_is_coldest_to_warmest = TemperatureCache.is_between( + self.input_hct.hue, coldest_hue, warmest_hue + ) + start_hue = warmest_hue if start_hue_is_coldest_to_warmest else coldest_hue + end_hue = coldest_hue if start_hue_is_coldest_to_warmest else warmest_hue + direction_of_rotation = 1.0 + smallest_error = 1000.0 + answer = self.hcts_by_hue[round(self.input_hct.hue)] + + complement_relative_temp = 1.0 - self.input_relative_temperature + for hue_addend in range(0, 360): + hue = sanitize_degrees_double( + start_hue + direction_of_rotation * hue_addend + ) + if not TemperatureCache.is_between(hue, start_hue, end_hue): + continue + possible_answer = self.hcts_by_hue[round(hue)] + relative_temp = ( + self.temps_by_hct[possible_answer] - coldest_temp + ) / range_temp + error = abs(complement_relative_temp - relative_temp) + if error < smallest_error: + smallest_error = error + answer = possible_answer + + self.complement_cache = answer + return self.complement_cache + + def relative_temperature(self, hct: Hct) -> float: + range_temp = self.temps_by_hct[self.warmest] - self.temps_by_hct[self.coldest] + difference_from_coldest = ( + self.temps_by_hct[hct] - self.temps_by_hct[self.coldest] + ) + if range_temp == 0.0: + return 0.5 + return difference_from_coldest / range_temp + + @property + def input_relative_temperature(self) -> float: + if self.input_hct_relative_temperature_cache >= 0.0: + return self.input_hct_relative_temperature_cache + + self.input_hct_relative_temperature_cache = self.relative_temperature( + self.input_hct + ) + return self.input_hct_relative_temperature_cache + + @property + def temps_by_hct(self) -> dict[Hct:float]: + if self.temps_by_hct_cache: + return self.temps_by_hct_cache + + all_hcts = self.hcts_by_hue + [self.input_hct] + temperatures_by_hct = {} + for e in all_hcts: + temperatures_by_hct[e] = TemperatureCache.raw_temperature(e) + + self.temps_by_hct_cache = temperatures_by_hct + return temperatures_by_hct + + @property + def hcts_by_hue(self) -> list[Hct]: + if self.hcts_by_hue_cache: + return self.hcts_by_hue_cache + + hcts = [ + Hct.from_hct(hue, self.input_hct.chroma, self.input_hct.tone) + for hue in range(0, 361) + ] + self.hcts_by_hue_cache = hcts + return hcts + + @staticmethod + def is_between(angle: float, a: float, b: float) -> bool: + if a < b: + return a <= angle <= b + return a <= angle or angle <= b + + @staticmethod + def raw_temperature(color: Hct) -> float: + lab = lab_from_argb(color.to_int()) + hue = sanitize_degrees_double(math.atan2(lab[2], lab[1]) * 180.0 / math.pi) + chroma = math.sqrt((lab[1] * lab[1]) + (lab[2] * lab[2])) + temperature = -0.5 + 0.02 * (chroma**1.07) * math.cos( + sanitize_degrees_double(hue - 50.0) * math.pi / 180.0 + ) + return temperature diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__init__.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/__init__.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..7796e27 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/__init__.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/color_utils.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/color_utils.cpython-311.pyc new file mode 100644 index 0000000..28a9381 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/color_utils.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/math_utils.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/math_utils.cpython-311.pyc new file mode 100644 index 0000000..fd1ece8 Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/math_utils.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/platform_utils.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/platform_utils.cpython-311.pyc new file mode 100644 index 0000000..0bde00c Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/platform_utils.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/theme_utils.cpython-311.pyc b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/theme_utils.cpython-311.pyc new file mode 100644 index 0000000..037a27a Binary files /dev/null and b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/__pycache__/theme_utils.cpython-311.pyc differ diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/color_utils.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/color_utils.py new file mode 100644 index 0000000..02c4e32 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/color_utils.py @@ -0,0 +1,211 @@ +from materialyoucolor.utils.math_utils import matrix_multiply, clamp_int + +SRGB_TO_XYZ = [ + [0.41233895, 0.35762064, 0.18051042], + [0.2126, 0.7152, 0.0722], + [0.01932141, 0.11916382, 0.95034478], +] + +XYZ_TO_SRGB = [ + [ + 3.2413774792388685, + -1.5376652402851851, + -0.49885366846268053, + ], + [ + -0.9691452513005321, + 1.8758853451067872, + 0.04156585616912061, + ], + [ + 0.05562093689691305, + -0.20395524564742123, + 1.0571799111220335, + ], +] + +WHITE_POINT_D65 = [95.047, 100.0, 108.883] + + +def argb_from_rgb(red: float, green: float, blue: float, alpha=255) -> int: + return ( + alpha << 24 | (int(red) & 255) << 16 | (int(green) & 255) << 8 | int(blue) & 255 + ) + + +def argb_from_linrgb(linrgb: list[float]) -> int: + r = delinearized(linrgb[0]) + g = delinearized(linrgb[1]) + b = delinearized(linrgb[2]) + return argb_from_rgb(r, g, b) + + +def alpha_from_argb(argb) -> float: + return (argb >> 24) & 255 + + +def red_from_argb(argb) -> float: + return (argb >> 16) & 255 + + +def green_from_argb(argb) -> float: + return (argb >> 8) & 255 + + +def blue_from_argb(argb) -> float: + return argb & 255 + + +def is_opaque(argb) -> bool: + return alpha_from_argb(argb) >= 255 + + +def argb_from_xyz(x, y, z) -> int: + matrix = XYZ_TO_SRGB + linear_r = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z + linear_g = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z + linear_b = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z + r = delinearized(linear_r) + g = delinearized(linear_g) + b = delinearized(linear_b) + return argb_from_rgb(r, g, b) + + +def xyz_from_argb(argb) -> list[float]: + r = linearized(red_from_argb(argb)) + g = linearized(green_from_argb(argb)) + b = linearized(blue_from_argb(argb)) + return matrix_multiply([r, g, b], SRGB_TO_XYZ) + + +def argb_from_lab(l, a, b) -> float: + white_point = WHITE_POINT_D65 + fy = (l + 16.0) / 116.0 + fx = a / 500.0 + fy + fz = fy - b / 200.0 + x_normalized = lab_invf(fx) + y_normalized = lab_invf(fy) + z_normalized = lab_invf(fz) + x = x_normalized * white_point[0] + y = y_normalized * white_point[1] + z = z_normalized * white_point[2] + return argb_from_xyz(x, y, z) + + +def lab_from_argb(argb: int) -> list[float]: + linear_r = linearized(red_from_argb(argb)) + linear_g = linearized(green_from_argb(argb)) + linear_b = linearized(blue_from_argb(argb)) + matrix = SRGB_TO_XYZ + x = matrix[0][0] * linear_r + matrix[0][1] * linear_g + matrix[0][2] * linear_b + y = matrix[1][0] * linear_r + matrix[1][1] * linear_g + matrix[1][2] * linear_b + z = matrix[2][0] * linear_r + matrix[2][1] * linear_g + matrix[2][2] * linear_b + white_point = WHITE_POINT_D65 + x_normalized = x / white_point[0] + y_normalized = y / white_point[1] + z_normalized = z / white_point[2] + fx = lab_f(x_normalized) + fy = lab_f(y_normalized) + fz = lab_f(z_normalized) + l = 116.0 * fy - 16.0 + a = 500.0 * (fx - fy) + b = 200.0 * (fy - fz) + return [l, a, b] + + +def argb_from_lstar(lstar: float) -> int: + y = y_from_lstar(lstar) + component = delinearized(y) + return argb_from_rgb(component, component, component) + + +def lstar_from_argb(argb: int) -> float: + y = xyz_from_argb(argb)[1] + return 116.0 * lab_f(y / 100.0) - 16.0 + + +def y_from_lstar(lstar: float) -> float: + return 100.0 * lab_invf((lstar + 16.0) / 116.0) + + +def srgb_to_argb(srgb): + return int("0xff{:06X}".format(0xFFFFFF & srgb), 16) + + +def lstar_from_y(y: float) -> float: + return lab_f(y / 100.0) * 116.0 - 16.0 + + +def linearized(rgb_component: float) -> float: + normalized = rgb_component / 255.0 + if normalized <= 0.040449936: + return normalized / 12.92 * 100.0 + else: + return pow((normalized + 0.055) / 1.055, 2.4) * 100.0 + + +def delinearized(rgb_component: float) -> float: + normalized = rgb_component / 100.0 + if normalized <= 0.0031308: + delinearized = normalized * 12.92 + else: + delinearized = 1.055 * pow(normalized, 1.0 / 2.4) - 0.055 + return clamp_int(0, 255, round(delinearized * 255)) + + +def white_point_d65() -> list[float]: + return WHITE_POINT_D65 + + +class Rgba: + r: float + g: float + b: float + a: float + + +def rgba_from_argb(argb: int) -> list[float]: + r = red_from_argb(argb) + g = green_from_argb(argb) + b = blue_from_argb(argb) + a = alpha_from_argb(argb) + return [r, g, b, a] + + +def argb_from_rgba(rgba: list[int]) -> int: + r_value = clamp_component(rgba[0]) + g_value = clamp_component(rgba[1]) + b_value = clamp_component(rgba[2]) + a_value = clamp_component(rgba[3]) + return (a_value << 24) | (r_value << 16) | (g_value << 8) | b_value + + +def argb_from_rgba_01(rgba: list[int]) -> int: + return argb_from_rgba([int(_ * 255) for _ in rgba]) + + +def clamp_component(value: int) -> int: + if value < 0: + return 0 + if value > 255: + return 255 + return value + + +def lab_f(t: float) -> float: + e = 216.0 / 24389.0 + kappa = 24389.0 / 27.0 + if t > e: + return pow(t, 1.0 / 3.0) + else: + return (kappa * t + 16) / 116 + + +def lab_invf(ft: float) -> float: + e = 216.0 / 24389.0 + kappa = 24389.0 / 27.0 + ft3 = ft * ft * ft + if ft3 > e: + return ft3 + else: + return (116 * ft - 16) / kappa diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/math_utils.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/math_utils.py new file mode 100644 index 0000000..246cb9b --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/math_utils.py @@ -0,0 +1,57 @@ +def signum(num: float) -> float: + if num < 0: + return -1.0 + elif num == 0: + return 0.0 + else: + return 1.0 + + +def lerp(start: float, stop: float, amount: float) -> float: + return (1.0 - amount) * start + amount * stop + + +def clamp_int(min_val: float, max_val: float, input_val: float) -> float: + if input_val < min_val: + return min_val + elif input_val > max_val: + return max_val + return input_val + + +def clamp_double(min_val: float, max_val: float, input_val: float) -> float: + if input_val < min_val: + return min_val + elif input_val > max_val: + return max_val + return input_val + + +def sanitize_degrees_int(degrees: float) -> float: + degrees = degrees % 360.0 + if degrees < 0: + degrees += 360.0 + return degrees + + +def sanitize_degrees_double(degrees: float) -> float: + degrees = degrees % 360.0 + if degrees < 0: + degrees += 360.0 + return degrees + + +def rotation_direction(from_angle: float, to_angle: float) -> float: + increasing_difference = sanitize_degrees_double(to_angle - from_angle) + return 1.0 if increasing_difference <= 180.0 else -1.0 + + +def difference_degrees(a: float, b: float) -> float: + return 180.0 - abs(abs(a - b) - 180.0) + + +def matrix_multiply(row: list[float], matrix: list[list[float]]) -> list[float]: + a = row[0] * matrix[0][0] + row[1] * matrix[0][1] + row[2] * matrix[0][2] + b = row[0] * matrix[1][0] + row[1] * matrix[1][1] + row[2] * matrix[1][2] + c = row[0] * matrix[2][0] + row[1] * matrix[2][1] + row[2] * matrix[2][2] + return [a, b, c] diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/platform_utils.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/platform_utils.py new file mode 100644 index 0000000..6043f8a --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/platform_utils.py @@ -0,0 +1,327 @@ +import json +import os +from glob import glob as path_find +import math +from timeit import default_timer + +from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot +from materialyoucolor.scheme.scheme_expressive import SchemeExpressive +from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad +from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome +from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow +from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant +from materialyoucolor.scheme.scheme_neutral import SchemeNeutral +from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity +from materialyoucolor.scheme.scheme_content import SchemeContent + +from materialyoucolor.scheme.dynamic_scheme import DynamicSchemeOptions, DynamicScheme +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.scheme.variant import Variant +from materialyoucolor.utils.color_utils import argb_from_rgba_01, srgb_to_argb +from materialyoucolor.utils.math_utils import sanitize_degrees_double +from materialyoucolor.hct import Hct +from materialyoucolor.score.score import Score +from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors + +try: + from materialyoucolor.quantize import QuantizeCelebi +except: + QuantizeCelebi = None + +autoclass = None + +try: + from jnius import autoclass + from android import mActivity + + Integer = autoclass("java.lang.Integer") + BuildVERSION = autoclass("android.os.Build$VERSION") + context = mActivity.getApplicationContext() + WallpaperManager = autoclass("android.app.WallpaperManager").getInstance(mActivity) +except Exception: + pass + +try: + from PIL import Image +except Exception: + Image = None + +SCHEMES = { + "TONAL_SPOT": SchemeTonalSpot, + "SPRITZ": SchemeNeutral, + "VIBRANT": SchemeVibrant, + "EXPRESSIVE": SchemeExpressive, + "FRUIT_SALAD": SchemeFruitSalad, + "RAINBOW": SchemeRainbow, + "MONOCHROME": SchemeMonochrome, + "FIDELITY": SchemeFidelity, + "CONTENT": SchemeContent, +} + +OPTION_THEME_STYLE = "android.theme.customization.theme_style" +COLOR_NAMES = { + "primary_palette": "system_accent1_{}", + "secondary_palette": "system_accent2_{}", + "tertiary_palette": "system_accent3_{}", + "neutral_palette": "system_neutral1_{}", + "neutral_variant_palette": "system_neutral2_{}", +} +APPROX_TONE = 200 +APPROX_CHROMA = 50 +DEFAULT_RESIZE_BITMAP_AREA = 112 * 112 + +WALLPAPER_CACHE = {} + + +def _is_android() -> bool: + try: + from android import mActivity + + return True + except Exception as e: + pass + return False + + +def save_and_resize_bitmap(drawable, path): + CompressFormat = autoclass("android.graphics.Bitmap$CompressFormat") + FileOutputStream = autoclass("java.io.FileOutputStream") + Bitmap = autoclass("android.graphics.Bitmap") + BitmapConfig = autoclass("android.graphics.Bitmap$Config") + Canvas = autoclass("android.graphics.Canvas") + bitmap = Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + BitmapConfig.ARGB_8888, + ) + canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()) + drawable.draw(canvas) + bitmap_area = bitmap.getWidth() * bitmap.getHeight() + scale_ratio = -1 + + if bitmap_area > DEFAULT_RESIZE_BITMAP_AREA: + scale_ratio = math.sqrt(DEFAULT_RESIZE_BITMAP_AREA / bitmap_area) + + if scale_ratio >= 0: + bitmap = Bitmap.createScaledBitmap( + bitmap, + math.ceil(bitmap.getWidth() * scale_ratio), + math.ceil(bitmap.getHeight() * scale_ratio), + False, + ) + bitmap.compress( + CompressFormat.PNG, + 100, + FileOutputStream(path), + ) + return bitmap.getWidth(), bitmap.getHeight() + + +def reverse_color_from_primary(color, scheme): + # TODO: Find solution + # Here we are using APPROX_TONE and APPROX_CHROMA + # because information is lost. + # Which likely will affect these colors: + # primaryContainer, tertiaryContainer + temp_hct = Hct.from_int(color) + reversed_color = color + if scheme in ["TONAL_SPOT", "SPRITZ", "VIBRANT", "RAINBOW", "CHROMA"]: + reversed_color = Hct.from_hct(temp_hct.hue, APPROX_CHROMA, APPROX_TONE).to_int() + elif scheme == "EXPRESSIVE": + reversed_color = Hct.from_hct( + sanitize_degrees_double(temp_hct.hue - 240.0), APPROX_CHROMA, APPROX_TONE + ).to_int() + elif scheme == "FRUIT_SALAD": + reversed_color = Hct.from_hct( + sanitize_degrees_double(temp_hct.hue + 50.0), APPROX_CHROMA, APPROX_TONE + ).to_int() + elif scheme in ["FIDELITY", "CONTENT"]: + # We have chroma info same as source here! + reversed_color = Hct.from_hct( + temp_hct.hue, temp_hct.chroma, APPROX_TONE + ).to_int() + return reversed_color + + +def _get_android_12_above( + logger, selected_scheme="TONAL_SPOT", contrast=0.0, dark_mode=False +) -> DynamicScheme: + SettingsSecure = autoclass("android.provider.Settings$Secure") + theme_settings = json.loads( + SettingsSecure.getString( + context.getContentResolver(), + SettingsSecure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + ) + ) + # Android 14 has this method + try: + contrast = mActivity.getSystemService(context.UI_MODE_SERVICE).getContrast() + logger("Got contrast '{}'".format(contrast)) + except Exception: + pass + + # See if system supports mutiple schemes + if OPTION_THEME_STYLE in theme_settings.keys(): + selected_scheme = theme_settings[OPTION_THEME_STYLE] + logger("Got system theme style '{}'".format(selected_scheme)) + + # Get system colors + get_system_color = lambda color_name: srgb_to_argb( + context.getColor( + context.getResources().getIdentifier( + COLOR_NAMES[color_name].format(APPROX_TONE), + "color", + "android", + ) + ) + ) + color_names = COLOR_NAMES.copy() + for color_name in COLOR_NAMES.keys(): + hct = Hct.from_int(get_system_color(color_name)) + color_names[color_name] = TonalPalette.from_hue_and_chroma(hct.hue, hct.chroma) + + return DynamicScheme( + DynamicSchemeOptions( + reverse_color_from_primary( + get_system_color("primary_palette"), + selected_scheme, + ), + getattr(Variant, selected_scheme), + contrast, + dark_mode, + **color_names, + ) + ) + + +def open_wallpaper_file(file_path) -> Image: + try: + return Image.open(file_path) + except Exception: + return None + + +def get_dynamic_scheme( + # Scheme options + dark_mode=True, + contrast=0.0, + dynamic_color_quality=10, + # Fallbacks + fallback_wallpaper_path=None, + fallback_scheme_name="TONAL_SPOT", + force_fallback_wallpaper=False, + # Logging + message_logger=print, + logger_head="MaterialYouColor", +) -> DynamicScheme: + logger = lambda message: message_logger(logger_head + " : " + message) + + is_android = _is_android() + selected_scheme = None + selected_color = None + + if is_android: + # For Android 12 and 12+ + if BuildVERSION.SDK_INT >= 31: + selected_scheme = _get_android_12_above( + logger, selected_scheme, contrast, dark_mode + ) + + # For Android 8.1 and 8.1+ + elif BuildVERSION.SDK_INT >= 27: + logger("Device doesn't supports MaterialYou") + selected_color = argb_from_rgba_01( + WallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM) + .getPrimaryColor() + .getComponents() + ) + logger("Got top color from wallpaper '{}'".format(selected_color)) + + # Lower than 8.1 + elif not force_fallback_wallpaper: + logger( + "Device does neither supports materialyoucolor " + "nor provides pregenerated colors" + ) + wallpaper_store_dir = context.getFilesDir().getAbsolutePath() + wallpaper_file = ".wallpaper-{}.png".format( + WallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) + ) + fallback_wallpaper_path = os.path.join(wallpaper_store_dir, wallpaper_file) + + if not os.path.isfile(fallback_wallpaper_path): + previous_files = path_find( + os.path.join(wallpaper_store_dir, ".wallpaper-*.png") + ) + [os.remove(file) for file in previous_files] + try: + # Requires `android.permission.READ_EXTERNAL_STORAGE` permission + wallpaper_drawable = WallpaperManager.getDrawable() + width, height = save_and_resize_bitmap( + wallpaper_drawable, fallback_wallpaper_path + ) + logger( + "Resized the system wallpaper : '{}x{}'".format(width, height) + ) + except Exception as e: + logger("Failed to get system wallpaper : " + str(e)) + fallback_wallpaper_path = None + + if all( + [ + not selected_color, + not selected_scheme, + fallback_wallpaper_path in WALLPAPER_CACHE.keys() + and WALLPAPER_CACHE[fallback_wallpaper_path][1] + == os.path.getsize(fallback_wallpaper_path), + ] + ): + logger( + "Got wallpaper color from cache '{}'".format( + WALLPAPER_CACHE[fallback_wallpaper_path][0] + ) + ) + selected_color = WALLPAPER_CACHE[fallback_wallpaper_path][0] + + if ( + not selected_scheme + and not selected_color + and fallback_wallpaper_path + and (image := open_wallpaper_file(fallback_wallpaper_path)) + and QuantizeCelebi is not None + ): + timer_start = default_timer() + pixel_len = image.width * image.height + image_data = image.getdata() + # TODO: Think about getting data from bitmap + pixel_array = [ + image_data[_] + for _ in range(0, pixel_len, dynamic_color_quality if not is_android else 1) + ] + logger( + f"Created an array of pixels from a " + f"system wallpaper file - {default_timer() - timer_start} sec." + ) + timer_start = default_timer() + colors = QuantizeCelebi(pixel_array, 128) + selected_color = Score.score(colors)[0] + WALLPAPER_CACHE[fallback_wallpaper_path] = [ + selected_color, + os.path.getsize(fallback_wallpaper_path), + ] + logger(f"Got dominant colors - " f"{default_timer() - timer_start} sec.") + + return ( + ( + SCHEMES[fallback_scheme_name]( + Hct.from_int(selected_color), + dark_mode, + contrast, + ) + if selected_color + else None + ) + if not selected_scheme + else selected_scheme + ) diff --git a/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/theme_utils.py b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/theme_utils.py new file mode 100644 index 0000000..18c30d3 --- /dev/null +++ b/kivy_venv/lib/python3.11/site-packages/materialyoucolor/utils/theme_utils.py @@ -0,0 +1,72 @@ +from materialyoucolor.blend import Blend +from materialyoucolor.palettes.core_palette import CorePalette +from materialyoucolor.palettes.tonal_palette import TonalPalette +from materialyoucolor.scheme import Scheme +from materialyoucolor.utils.image_utils import source_color_from_image +from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer +from materialyoucolor.hct import Hct + + +class Theme: + def __init__( + self, + source: int, + schemes: dict, + palettes: dict, + custom_colors: list[dict], + ): + self.source = source + self.schemes = schemes + self.palettes = palettes + self.custom_colors = custom_colors + + +def custom_color(custom_color, source_color=None, blend=False): + value = DislikeAnalyzer.fix_if_disliked(Hct.from_int(custom_color)) + if blend: + value = Blend.harmonize(value, source_color) + palette = CorePalette.of(value) + tones = palette.a1 + return { + "color": custom_color, + "theme_color": source_color, + "blended": blend, + "light": { + "color": tones.tone(40), + "onColor": tones.tone(100), + "colorContainer": tones.tone(90), + "onColorContainer": tones.tone(10), + }, + "dark": { + "color": tones.tone(80), + "onColor": tones.tone(20), + "colorContainer": tones.tone(30), + "onColorContainer": tones.tone(90), + }, + } + + +def theme_from_source_color( + source: int, custom_colors=[], fix_if_disliked=False +) -> Theme: + palette = CorePalette.of( + DislikeAnalyzer.fix_if_disliked(Hct.from_int(source)).to_int() + if fix_if_disliked + else source + ) + return Theme( + source, + {"light": Scheme.light(source), "dark": Scheme.dark(source)}, + { + "primary": palette.a1, + "secondary": palette.a2, + "tertiary": palette.a3, + "neutral": palette.n1, + "neutralVariant": palette.n2, + "error": palette.error, + }, + [ + custom_color(color, blend=True, source_color=source) + for color in custom_colors + ], + ) diff --git a/main.py b/main.py index 6fb2bcb..affcf99 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,54 @@ -from kivymd.app import App -from kivymd.uix.button import Button +from kivy.app import App +from kivy.uix.button import Button +from kivy.uix.gridlayout import GridLayout +from kivy.properties import ObjectProperty +from kivy.core.window import Window +import requests +# from kivy.network.urlrequest import UrlRequest + + + +# Window.size = (480, 853) + +# from kivy.config import Config +# Config.set('kivy', 'keyboard_mode', 'systemanddock') + +def isAvailable(ip): + try: + response = requests.get("http://" + ip, timeout=5) + # print("Yes") + return "Yes" + except requests.ConnectionError: + # return False + # print("No") + return "NO" + except requests.exceptions.InvalidURL: + return "?" + + + +class Container(GridLayout): + + def checkAvailability(self): + openwrt = "10.1.0.1" + polaris = "10.1.0.5" + raspberry_pi = "10.1.0.9" + udongein_xyz = "udongein.xyz" + + self.openwrt.text = isAvailable(openwrt) + self.polaris.text = isAvailable(polaris) + self.raspberry_pi.text = isAvailable(raspberry_pi) + self.udongein_xyz.text = isAvailable(udongein_xyz) + self.text_input_show.text = 'Other: ' + str(self.text_input.text) + self.text_input_check.text = isAvailable(str(self.text_input.text)) + class MyApp(App): + # theme_cls = ThemeManager() + Title = 'Uptime Check' + def build(self): - return Button(text="Meow world", halign="center") + # self.theme_cls.theme_style = 'Light' + return Container() MyApp().run() \ No newline at end of file diff --git a/my.kv b/my.kv new file mode 100644 index 0000000..ad4e686 --- /dev/null +++ b/my.kv @@ -0,0 +1,96 @@ + + +: + font_size: '25sp' + halign: 'left' + valign: 'middle' + text_size: self.size + +: + rows:3 + + text_input: text_input + text_input_check: text_input_check + text_input_show: text_input_show + openwrt: openwrt + polaris: polaris + raspberry_pi: raspberry_pi + udongein_xyz: udongein_xyz + + + AnchorLayout: + size_hint: 1, 0.15 + + TextInput: + id: text_input + font_size: '45sp' + multiline: False + input_type: 'number' + + GridLayout: + cols: 2 + + BoxLayout: + orientation: 'vertical' + padding: [30, 0, 0, 0] + + ItemLabel: + text: 'Router' + + ItemLabel: + text: 'Polaris' + + ItemLabel: + text: 'Raspberry pi' + + ItemLabel: + text: 'Udongein.xyz' + + ItemLabel: + id: text_input_show + text: 'Other: ' + + + + BoxLayout: + orientation: 'vertical' + size_hint: 0.5, 1 + + ItemLabel: + id: openwrt + text: '[b]?[/b]' + markup: True + + ItemLabel: + id: polaris + text: '[b]?[/b]' + markup: True + + ItemLabel: + id: raspberry_pi + text: '[b]?[/b]' + markup: True + + ItemLabel: + id: udongein_xyz + text: '[b]?[/b]' + markup: True + + ItemLabel: + id: text_input_check + text: '[b]?[/b]' + markup: True + + + BoxLayout: + size_hint: 0.9, 0.15 + padding: [30, 0, 30, 20] + Button: + text: 'Check connection' + font_size: 40 + + on_release: + root.checkAvailability() + + +