first commit

This commit is contained in:
Yura 2024-09-15 15:12:16 +03:00
commit 417e54da96
5696 changed files with 900003 additions and 0 deletions

View file

@ -0,0 +1,4 @@
"""
Effects
=======
"""

View file

@ -0,0 +1 @@
from .fadingedge import FadingEdgeEffect

View file

@ -0,0 +1,200 @@
"""
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 dont 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)

View file

@ -0,0 +1 @@
from .roulettescroll import RouletteScrollEffect

View file

@ -0,0 +1,251 @@
"""
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")

View file

@ -0,0 +1 @@
from .stiffscroll import StiffScrollEffect

View file

@ -0,0 +1,215 @@
"""
Effects/StiffScrollEffect
=========================
An Effect to be used with ScrollView to prevent scrolling beyond
the bounds, but politely.
A ScrollView constructed with StiffScrollEffect,
eg. ScrollView(effect_cls=StiffScrollEffect), will get harder to
scroll as you get nearer to its edges. You can scroll all the way to
the edge if you want to, but it will take more finger-movement than
usual.
Unlike DampedScrollEffect, it is impossible to overscroll with
StiffScrollEffect. That means you cannot push the contents of the
ScrollView far enough to see what's beneath them. This is appropriate
if the ScrollView contains, eg., a background image, like a desktop
wallpaper. Overscrolling may give the impression that there is some
reason to overscroll, even if just to take a peek beneath, and that
impression may be misleading.
StiffScrollEffect was written by Zachary Spector. His other stuff is at:
https://github.com/LogicalDash/
He can be reached, and possibly hired, at:
zacharyspector@gmail.com
"""
from time import time
from kivy.animation import AnimationTransition
from kivy.effects.kinetic import KineticEffect
from kivy.properties import NumericProperty, ObjectProperty
from kivy.uix.widget import Widget
class StiffScrollEffect(KineticEffect):
drag_threshold = NumericProperty("20sp")
"""Minimum distance to travel before the movement is considered as a
drag.
:attr:`drag_threshold` is an :class:`~kivy.properties.NumericProperty`
and defaults to `'20sp'`.
"""
min = NumericProperty(0)
"""Minimum boundary to stop the scrolling at.
:attr:`min` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
max = NumericProperty(0)
"""Maximum boundary to stop the scrolling at.
:attr:`max` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
max_friction = NumericProperty(1)
"""How hard should it be to scroll, at the worst?
:attr:`max_friction` is an :class:`~kivy.properties.NumericProperty`
and defaults to `1`.
"""
body = NumericProperty(0.7)
"""Proportion of the range in which you can scroll unimpeded.
:attr:`body` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.7`.
"""
scroll = NumericProperty(0.0)
"""Computed value for scrolling
:attr:`scroll` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0.0`.
"""
transition_min = ObjectProperty(AnimationTransition.in_cubic)
"""The AnimationTransition function to use when adjusting the friction
near the minimum end of the effect.
:attr:`transition_min` is an :class:`~kivy.properties.ObjectProperty`
and defaults to :class:`kivy.animation.AnimationTransition`.
"""
transition_max = ObjectProperty(AnimationTransition.in_cubic)
"""The AnimationTransition function to use when adjusting the friction
near the maximum end of the effect.
:attr:`transition_max` is an :class:`~kivy.properties.ObjectProperty`
and defaults to :class:`kivy.animation.AnimationTransition`.
"""
target_widget = ObjectProperty(None, allownone=True, baseclass=Widget)
"""The widget to apply the effect to.
:attr:`target_widget` is an :class:`~kivy.properties.ObjectProperty`
and defaults to ``None``.
"""
displacement = NumericProperty(0)
"""The absolute distance moved in either direction.
:attr:`displacement` is an :class:`~kivy.properties.NumericProperty`
and defaults to `0`.
"""
def __init__(self, **kwargs):
"""Set ``self.base_friction`` to the value of ``self.friction`` just
after instantiation, so that I can reset to that value later.
"""
super().__init__(**kwargs)
self.base_friction = self.friction
def update_velocity(self, dt):
"""Before actually updating my velocity, meddle with ``self.friction``
to make it appropriate to where I'm at, currently.
"""
hard_min = self.min
hard_max = self.max
if hard_min > hard_max:
hard_min, hard_max = hard_max, hard_min
margin = (1.0 - self.body) * (hard_max - hard_min)
soft_min = hard_min + margin
soft_max = hard_max - margin
if self.value < soft_min:
try:
prop = (soft_min - self.value) / (soft_min - hard_min)
self.friction = self.base_friction + abs(
self.max_friction - self.base_friction
) * self.transition_min(prop)
except ZeroDivisionError:
pass
elif self.value > soft_max:
try:
# normalize how far past soft_max I've gone as a
# proportion of the distance between soft_max and hard_max
prop = (self.value - soft_max) / (hard_max - soft_max)
self.friction = self.base_friction + abs(
self.max_friction - self.base_friction
) * self.transition_min(prop)
except ZeroDivisionError:
pass
else:
self.friction = self.base_friction
return super().update_velocity(dt)
def on_value(self, *args):
"""Prevent moving beyond my bounds, and update ``self.scroll``"""
if self.value > self.min:
self.velocity = 0
self.scroll = self.min
elif self.value < self.max:
self.velocity = 0
self.scroll = self.max
else:
self.scroll = self.value
def start(self, val, t=None):
"""Start movement with ``self.friction`` = ``self.base_friction``"""
self.is_manual = True
t = t or time()
self.velocity = self.displacement = 0
self.friction = self.base_friction
self.history = [(t, val)]
def update(self, val, t=None):
"""Reduce the impact of whatever change has been made to me, in
proportion with my current friction.
"""
t = t or time()
hard_min = self.min
hard_max = self.max
if hard_min > hard_max:
hard_min, hard_max = hard_max, hard_min
gamut = hard_max - hard_min
margin = (1.0 - self.body) * gamut
soft_min = hard_min + margin
soft_max = hard_max - margin
distance = val - self.history[-1][1]
reach = distance + self.value
if (distance < 0 and reach < soft_min) or (
distance > 0 and soft_max < reach
):
distance -= distance * self.friction
self.apply_distance(distance)
self.history.append((t, val))
if len(self.history) > self.max_history:
self.history.pop(0)
self.displacement += abs(distance)
self.trigger_velocity_update()
def stop(self, val, t=None):
"""Work out whether I've been flung."""
self.is_manual = False
self.displacement += abs(val - self.history[-1][1])
if self.displacement <= self.drag_threshold:
self.velocity = 0
return super().stop(val, t)