Skip to content

Instantly share code, notes, and snippets.

@clayote
Created February 13, 2015 18:17
Show Gist options
  • Save clayote/9f67025b178adccb6587 to your computer and use it in GitHub Desktop.
Save clayote/9f67025b178adccb6587 to your computer and use it in GitHub Desktop.
Abbreviated card.py
from kivy.adapters.listadapter import ListAdapter
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.properties import (
BooleanProperty,
ListProperty,
NumericProperty,
ObjectProperty,
StringProperty
)
from kivy.uix.layout import Layout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.listview import ListView
class Card(FloatLayout):
dragging = BooleanProperty(False)
idx = NumericProperty()
bg_color = ListProperty([1, 1, 1, 1])
text_color = ListProperty([0, 0, 0, 1])
text = StringProperty('')
def get_kwargs(self):
return {
'bg_color': self.bg_color,
'text_color': self.text_color,
'text': self.text
}
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
return
if 'card' in touch.ud:
return
touch.grab(self)
touch.ud['card'] = self.get_kwargs()
touch.ud['idx'] = self.idx
touch.ud['layout'] = self.parent
self.dragging = True
self._collide_x = touch.x - self.x
self._collide_y = touch.y - self.y
def on_touch_move(self, touch):
if not self.dragging:
return
self.pos = (
touch.x - self._collide_x,
touch.y - self._collide_y
)
def on_touch_up(self, touch):
if not self.dragging:
return
self.dragging = False
class DeckView(ListView):
def on_touch_move(self, touch):
for child in self.children:
if child.collide_point(*touch.pos):
child.dispatch('on_touch_move', touch)
else:
child.insertion_point = None
class DeckLayout(Layout):
insertion_point = NumericProperty(None, allownone=True)
x_hint_step = NumericProperty(0.01)
y_hint_step = NumericProperty(-0.07)
adapter = ObjectProperty()
def point_before_card(self, card, x, y):
def ycmp():
if self.y_hint_step == 0:
return False
elif self.y_hint_step > 0:
# stacking upward
return y < card.y
else:
# stacking downward
return y > card.top
if self.x_hint_step > 0:
# stacking to the right
if x < card.x:
return True
return ycmp()
elif self.x_hint_step == 0:
return ycmp()
else:
# stacking to the left
if x > card.right:
return True
return ycmp()
def point_after_card(self, card, x, y):
def ycmp():
if self.y_hint_step == 0:
return False
elif self.y_hint_step > 0:
# stacking upward
return y > card.top
else:
# stacking downward
return y < card.y
if self.x_hint_step > 0:
# stacking to the right
if x > card.right:
return True
return ycmp()
elif self.x_hint_step == 0:
return ycmp()
else:
# stacking to the left
if x < card.x:
return True
return ycmp()
def do_layout(self, *args):
if self.size == [1, 1]:
return
cards = list(self.children)
inspt = self.insertion_point
# display a gap where the card would be inserted if you
# dropped it now
if inspt is not None and inspt > 0:
if inspt > len(cards):
cards.append(None)
else:
try:
dragidx = cards.index(next(c for c in cards if c.dragging))
del cards[dragidx]
except StopIteration:
pass
cards.insert(len(cards)-inspt, None)
else:
try:
cards.remove(next(c for c in cards if c.dragging))
except StopIteration:
pass
shw = 0.1
shh = 0.2
pos_hint = {'x': 0.05, 'top': 0.95}
w, h = self.size
x, y = self.pos
for child in cards:
if child is not None:
child.size = w * shw, h * shh
child.x = x + pos_hint['x'] * w
child.top = y + pos_hint['top'] * h
pos_hint['x'] += self.x_hint_step
pos_hint['top'] += self.y_hint_step
def on_parent(self, *args):
if self.parent is not None:
self._trigger_layout()
def on_children(self, *args):
self._trigger_layout()
def on_insertion_point(self, *args):
Logger.debug(
"DeckLayout: insertion point {}".format(self.insertion_point)
)
if self.insertion_point is not None:
self._trigger_layout()
def on_touch_move(self, touch):
if 'card' not in touch.ud:
Logger.debug(
"DeckLayout: no card, won't handle touch"
)
return
cards = [c for c in self.children if not c.dragging]
i = len(cards)
for card in cards:
if card.collide_point(*touch.pos):
if self.insertion_point != i:
self.insertion_point = i
return
i -= 1
else:
# it seems like these return false positives sometimes??
if self.insertion_point in (0, len(self.children)):
return
if self.point_before_card(
self.children[0], *touch.pos
):
self.insertion_point = len(self.children)
elif self.point_after_card(
self.children[-1], *touch.pos
):
self.insertion_point = 0
def on_touch_up(self, touch):
if 'card' not in touch.ud:
return
kwargs = touch.ud['card']
if self.insertion_point is not None and self.collide_point(*touch.pos):
del touch.ud['layout'].adapter.data[touch.ud['idx']]
self.adapter.data.insert(self.insertion_point, kwargs)
self.insertion_point = None
kv = """
<Card>:
canvas:
Color:
rgba: root.bg_color
Rectangle:
pos: root.pos
size: root.size
Color:
rgba: [0, 0, 0, 1]
Line:
points: [root.x, root.y, root.right, root.y, root.right, root.top, root.x, root.top, root.x, root.y]
Color:
rgba: [1, 1, 1, 1]
Label:
color: root.text_color
text: root.text
pos: root.pos
text_size: root.size
<DeckView>:
container: container
DeckLayout:
id: container
adapter: root.adapter
pos: root.pos
size: root.size
"""
Builder.load_string(kv)
if __name__ == '__main__':
from kivy.base import runTouchApp
from kivy.core.window import Window
from kivy.modules import inspector
from kivy.uix.boxlayout import BoxLayout
data0 = [{'text_color': [0, 0, 0, 1], 'text': 'deck 0 card {}'.format(i)} for i in range(0, 9)]
data1 = [{'text': 'deck 1 card {}'.format(i)} for i in range(0, 9)]
def args_converter(i, kv):
kv['idx'] = i
return kv
adapter0 = ListAdapter(data=data0, cls=Card, args_converter=args_converter)
adapter1 = ListAdapter(data=data1, cls=Card, args_converter=args_converter)
layout = BoxLayout(orientation='vertical')
view0 = DeckView(adapter=adapter0)
view1 = DeckView(adapter=adapter1)
layout.add_widget(view0)
layout.add_widget(view1)
inspector.create_inspector(Window, layout)
runTouchApp(layout)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment