Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stop using the word 'location' to represent the sizing/positioning p… #27

Merged
merged 1 commit into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,15 @@ class KXDraggableBehavior:
x=ctx.original_pos_win[0],
y=ctx.original_pos_win[1],
)
restore_widget_location(self, ctx.original_location)
restore_widget_state(self, ctx.original_state)
```

If you don't need the animation, and want the draggable to go back instantly, overwrite the handler as follows:

```python
class MyDraggable(KXDraggableBehavior, Widget):
def on_drag_fail(self, touch, ctx):
restore_widget_location(self, ctx.original_location)
restore_widget_state(self, ctx.original_state)
```

Or if you want the draggable to not go back, and want it to stay the current position, overwrite the handler as follows:
Expand Down
2 changes: 1 addition & 1 deletion README_jp.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ dragが失敗/成功/中止した時に何をするかは完全にあなたに
```python
class MyDraggable(KXDraggableBehavior, Widget):
def on_drag_fail(self, touch, ctx):
restore_widget_location(self, ctx.original_location)
restore_widget_state(self, ctx.original_state)
```

また何もせずにその場に残って欲しいなら以下のようにすれば良い。
Expand Down
6 changes: 3 additions & 3 deletions examples/flutter_style_draggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, NoTransition

from kivy_garden.draggable import KXDroppableBehavior, KXDraggableBehavior, restore_widget_location
from kivy_garden.draggable import KXDroppableBehavior, KXDraggableBehavior, restore_widget_state

KV_CODE = '''
<Cell>:
Expand Down Expand Up @@ -64,9 +64,9 @@ def __on_is_being_dragged(self, value):

def on_drag_start(self, touch, ctx):
if self.has_screen('childWhenDragging'):
restore_widget_location(
restore_widget_state(
self.get_screen('childWhenDragging'),
ctx.original_location,
ctx.original_state,
)
return super().on_drag_start(touch, ctx)

Expand Down
2 changes: 2 additions & 0 deletions src/kivy_garden/draggable/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
__all__ = (
'KXDraggableBehavior', 'KXDroppableBehavior', 'KXReorderableBehavior',
'save_widget_state', 'restore_widget_state',
'save_widget_location', 'restore_widget_location', 'ongoing_drags',
)

from ._impl import KXDraggableBehavior, KXDroppableBehavior, KXReorderableBehavior, ongoing_drags
from ._utils import save_widget_state, restore_widget_state
from ._utils import save_widget_location, restore_widget_location
39 changes: 25 additions & 14 deletions src/kivy_garden/draggable/_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from inspect import isawaitable
from dataclasses import dataclass
from contextlib import nullcontext
from functools import cached_property

from kivy.config import Config
from kivy.properties import (
BooleanProperty, ListProperty, StringProperty, NumericProperty, OptionProperty, AliasProperty,
)
Expand All @@ -20,7 +20,7 @@

from ._utils import (
temp_transform, temp_grab_current, _create_spacer,
save_widget_location, restore_widget_location,
save_widget_state, restore_widget_state,
)


Expand All @@ -31,13 +31,24 @@ class DragContext:
started. (window coordinates).
'''

original_location: dict = None
'''(read-only) Where the draggable came from. This can be passed to ``restore_widget_location()``. '''
original_state: dict = None
'''
(read-only) The state of the draggable at the time the drag has started.
This can be passed to ``restore_widget_state()``.
'''

droppable: Union[None, 'KXDroppableBehavior', 'KXReorderableBehavior'] = None
'''(read-only) The widget where the draggable dropped to. This is always None on_drag_start/on_drag_cancel, and is
always a widget on_drag_succeed, and can be either on_drag_fail/on_drag_end.'''

@cached_property
def original_location(self) -> dict:
'''
This exists solely for backward compatibility.
Use :attr:`original_state` instead.
'''
return self.original_state


class KXDraggableBehavior:
__events__ = (
Expand Down Expand Up @@ -155,10 +166,10 @@ async def _treat_a_touch_as_a_drag(self, touch, *, do_transform=False, touch_rec
from kivy.core.window import Window
window = Window
touch_ud = touch.ud
original_location = save_widget_location(self)
original_state = save_widget_state(self)
ctx = DragContext(
original_pos_win=original_pos_win,
original_location=original_location,
original_state=original_state,
)

# move self under the Window
Expand Down Expand Up @@ -252,11 +263,11 @@ def on_drag_end(self, touch, ctx: DragContext):
pass

def on_drag_succeed(self, touch, ctx: DragContext):
original_location = ctx.original_location
original_state = ctx.original_state
self.parent.remove_widget(self)
self.size_hint_x = original_location['size_hint_x']
self.size_hint_y = original_location['size_hint_y']
self.pos_hint = original_location['pos_hint']
self.size_hint_x = original_state['size_hint_x']
self.size_hint_y = original_state['size_hint_y']
self.pos_hint = original_state['pos_hint']
ctx.droppable.add_widget(self, index=touch.ud.get('kivyx_droppable_index', 0))

async def on_drag_fail(self, touch, ctx: DragContext):
Expand All @@ -265,10 +276,10 @@ async def on_drag_fail(self, touch, ctx: DragContext):
x=ctx.original_pos_win[0],
y=ctx.original_pos_win[1],
)
restore_widget_location(self, ctx.original_location)
restore_widget_state(self, ctx.original_state)

def on_drag_cancel(self, touch, ctx: DragContext):
restore_widget_location(self, ctx.original_location)
restore_widget_state(self, ctx.original_state)


def ongoing_drags(*, window=kivy.core.window.Window) -> List[KXDraggableBehavior]:
Expand Down Expand Up @@ -376,9 +387,9 @@ async def _watch_touch(self, touch):
touch_ud = touch.ud

try:
restore_widget_location(
restore_widget_state(
spacer,
touch_ud['kivyx_drag_ctx'].original_location,
touch_ud['kivyx_drag_ctx'].original_state,
ignore_parent=True)
add_widget(spacer)
async with ak.watch_touch(self, touch) as is_touch_move:
Expand Down
34 changes: 20 additions & 14 deletions src/kivy_garden/draggable/_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__all__ = (
'temp_transform', 'temp_grab_current',
'save_widget_state', 'restore_widget_state',
'save_widget_location', 'restore_widget_location',
)

Expand Down Expand Up @@ -45,29 +46,29 @@ def __exit__(self, *args):
)


def save_widget_location(widget, *, ignore_parent=False) -> dict:
def save_widget_state(widget, *, ignore_parent=False) -> dict:
w = widget.__self__
location = {name: getattr(w, name) for name in _shallow_copyable_property_names}
location['pos_hint'] = deepcopy(w.pos_hint)
state = {name: getattr(w, name) for name in _shallow_copyable_property_names}
state['pos_hint'] = deepcopy(w.pos_hint)
if ignore_parent:
return location
return state
parent = w.parent
if parent is None:
location['weak_parent'] = None
state['weak_parent'] = None
else:
location['weak_parent'] = ref(parent)
location['index'] = parent.children.index(w)
return location
state['weak_parent'] = ref(parent)
state['index'] = parent.children.index(w)
return state


def restore_widget_location(widget, location: dict, *, ignore_parent=False):
def restore_widget_state(widget, state: dict, *, ignore_parent=False):
w = widget.__self__
for name in _shallow_copyable_property_names:
setattr(w, name, location[name])
w.pos_hint = deepcopy(location['pos_hint'])
if ignore_parent or 'weak_parent' not in location:
setattr(w, name, state[name])
w.pos_hint = deepcopy(state['pos_hint'])
if ignore_parent or 'weak_parent' not in state:
return
weak_parent = location['weak_parent']
weak_parent = state['weak_parent']
if weak_parent is None:
parent = None
else:
Expand All @@ -85,7 +86,7 @@ def restore_widget_location(widget, location: dict, *, ignore_parent=False):
if parent.parent is parent:
parent.add_widget(w)
else:
parent.add_widget(w, index=location['index'])
parent.add_widget(w, index=state['index'])


def _create_spacer(**kwargs):
Expand All @@ -106,3 +107,8 @@ def _create_spacer(**kwargs):
size=lambda __, value: setattr(rect_inst, 'size', value),
)
return spacer


# leave the old names for backward compatibility
save_widget_location = save_widget_state
restore_widget_location = restore_widget_state
1 change: 1 addition & 0 deletions tests/test_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
def test_flower():
from kivy_garden.draggable import (
KXDraggableBehavior, KXDroppableBehavior, KXReorderableBehavior,
restore_widget_state, save_widget_state,
restore_widget_location, save_widget_location, ongoing_drags,
)
77 changes: 37 additions & 40 deletions tests/test_utils_restore_widget_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@


@pytest.fixture(scope='module')
def location_factory():
def state_factory():
from copy import deepcopy
loc = {
state = {
'x': 2, 'y': 2, 'width': 2, 'height': 2,
'size_hint_x': 2, 'size_hint_y': 2,
'pos_hint': {'center': [2, 2, ], },
'size_hint_min_x': 2, 'size_hint_min_y': 2,
'size_hint_max_x': 2, 'size_hint_max_y': 2,
}
return lambda: deepcopy(loc)
return lambda: deepcopy(state)


@pytest.mark.parametrize('ignore_parent', (True, False, ))
def test_sizing_info(location_factory, ignore_parent):
def test_sizing_info(state_factory, ignore_parent):
from kivy.uix.widget import Widget
from kivy_garden.draggable import restore_widget_location
from kivy_garden.draggable import restore_widget_state
w = Widget()
loc = location_factory()
restore_widget_location(w, loc, ignore_parent=ignore_parent)
loc['width'] = 0
loc['x'] = 0
loc['size_hint_x'] = 0
loc['pos_hint']['center'][0] = 0
loc['size_hint_min_x'] = 0
loc['size_hint_min_y'] = 0
state = state_factory()
restore_widget_state(w, state, ignore_parent=ignore_parent)
state['width'] = 0
state['x'] = 0
state['size_hint_x'] = 0
state['pos_hint']['center'][0] = 0
state['size_hint_min_x'] = 0
state['size_hint_min_y'] = 0
assert w.size == [2, 2, ]
assert w.pos == [2, 2, ]
assert w.size_hint == [2, 2, ]
Expand All @@ -37,16 +37,16 @@ def test_sizing_info(location_factory, ignore_parent):

@pytest.mark.parametrize('ignore_parent', (True, False, ))
@pytest.mark.parametrize('has_parent', (True, False, ))
def test_weak_parent_is_none(location_factory, ignore_parent, has_parent):
def test_weak_parent_is_none(state_factory, ignore_parent, has_parent):
from kivy.uix.widget import Widget
from kivy_garden.draggable import restore_widget_location
loc = location_factory()
loc['weak_parent'] = None
from kivy_garden.draggable import restore_widget_state
state = state_factory()
state['weak_parent'] = None
w = Widget()
if has_parent:
parent = Widget()
parent.add_widget(w)
restore_widget_location(w, loc, ignore_parent=ignore_parent)
restore_widget_state(w, state, ignore_parent=ignore_parent)
if ignore_parent and has_parent:
assert w.parent is parent
else:
Expand All @@ -55,17 +55,16 @@ def test_weak_parent_is_none(location_factory, ignore_parent, has_parent):

@pytest.mark.parametrize('ignore_parent', (True, False, ))
@pytest.mark.parametrize('has_parent', (True, False, ))
def test_weak_parent_not_in_the_keys(
location_factory, ignore_parent, has_parent):
def test_weak_parent_not_in_the_keys(state_factory, ignore_parent, has_parent):
from kivy.uix.widget import Widget
from kivy_garden.draggable import restore_widget_location
loc = location_factory()
assert 'weak_parent' not in loc
from kivy_garden.draggable import restore_widget_state
state = state_factory()
assert 'weak_parent' not in state
w = Widget()
if has_parent:
parent = Widget()
parent.add_widget(w)
restore_widget_location(w, loc, ignore_parent=ignore_parent)
restore_widget_state(w, state, ignore_parent=ignore_parent)
if has_parent:
assert w.parent is parent
else:
Expand All @@ -74,22 +73,21 @@ def test_weak_parent_not_in_the_keys(

@pytest.mark.parametrize('ignore_parent', (True, False, ))
@pytest.mark.parametrize('has_parent', (True, False, ))
def test_weak_parent_is_alive(
location_factory, ignore_parent, has_parent):
def test_weak_parent_is_alive(state_factory, ignore_parent, has_parent):
import weakref
from kivy.uix.widget import Widget
from kivy_garden.draggable import restore_widget_location
from kivy_garden.draggable import restore_widget_state
prev_parent = Widget()
loc = location_factory()
loc['weak_parent'] = weakref.ref(prev_parent)
loc['index'] = 0
state = state_factory()
state['weak_parent'] = weakref.ref(prev_parent)
state['index'] = 0
w = Widget()
if has_parent:
parent = Widget()
parent.add_widget(w)
parent.add_widget(Widget())
assert parent.children.index(w) == 1
restore_widget_location(w, loc, ignore_parent=ignore_parent)
restore_widget_state(w, state, ignore_parent=ignore_parent)
if ignore_parent:
if has_parent:
assert w.parent is parent
Expand All @@ -103,28 +101,27 @@ def test_weak_parent_is_alive(

@pytest.mark.parametrize('ignore_parent', (True, False, ))
@pytest.mark.parametrize('has_parent', (True, False, ))
def test_weak_parent_is_dead(
location_factory, ignore_parent, has_parent):
def test_weak_parent_is_dead(state_factory, ignore_parent, has_parent):
import gc
import weakref
from kivy.uix.widget import Widget
from kivy_garden.draggable import restore_widget_location
loc = location_factory()
loc['weak_parent'] = weakref.ref(Widget())
loc['index'] = 0
from kivy_garden.draggable import restore_widget_state
state = state_factory()
state['weak_parent'] = weakref.ref(Widget())
state['index'] = 0
gc.collect()
assert loc['weak_parent']() is None
assert state['weak_parent']() is None
w = Widget()
if has_parent:
parent = Widget()
parent.add_widget(w)
parent.add_widget(Widget())
assert parent.children.index(w) == 1
if ignore_parent:
restore_widget_location(w, loc, ignore_parent=True)
restore_widget_state(w, state, ignore_parent=True)
else:
with pytest.raises(ReferenceError):
restore_widget_location(w, loc, ignore_parent=False)
restore_widget_state(w, state, ignore_parent=False)
if has_parent:
assert w.parent is parent
assert parent.children.index(w) == 1
Expand Down
Loading