test-kivy-app/kivy_venv/lib/python3.11/site-packages/kivymd/uix/scrollview.py
2024-09-15 20:57:02 +03:00

338 lines
10 KiB
Python

"""
Components/ScrollView
=====================
.. versionadded:: 1.0.0
:class:`~kivy.uix.scrollview.ScrollView` class equivalent.
It implements Material Design's overscorll effect and
simplifies working with some widget properties. For example:
ScrollView
----------
.. code-block:: kv
ScrollView:
canvas:
Color:
rgba: app.theme_cls.primaryColor
Rectangle:
pos: self.pos
size: self.size
MDScrollView
------------
.. code-block:: kv
MDScrollView:
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
"""
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 BackgroundColorBehavior, DeclarativeBehavior
class StretchOverScrollStencil(ScrollEffect):
"""
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)