-
Notifications
You must be signed in to change notification settings - Fork 0
/
arrow.py
148 lines (115 loc) · 3.93 KB
/
arrow.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
10-19-15
"""
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, ListProperty
from kivy.lang import Builder
class Arrow(Widget):
start = ObjectProperty(None)
end = ObjectProperty(None, allownone=True)
start_pos = ListProperty([0, 0])
end_pos = ListProperty([0, 0])
color = ListProperty([1, 1, 1, 1])
end_color = ListProperty([1, 1, 1, 1])
start_color = ListProperty([1, 1, 1, 1])
def __init__(self, **kw):
super(Arrow, self).__init__(**kw)
self.make_rebinder(
'start',
{'pos': self._update_start,
'size': self._update_start})
self.make_rebinder(
'end',
{'pos': self._update_end,
'size': self._update_end})
# name of object (target) -> create prev (_prev_target)
# dict of bindings:
# {
# 'pos': method,
# ...
# }
def make_rebinder(self, propname, bindings):
"""
This method generates a method that will unbind/rebind the given
bindings to the given property's properties when the property changes.
Example:
self is some kind of Widget
propname is the name of an ObjectProperty pointing to an instance
of a Label
"""
# Create the previous property.
setattr(self, '_prev_' + propname, None)
def rebinder(self, *ar):
# Get property and previous property.
prop = getattr(self, propname)
prev_prop = getattr(self, '_prev_' + propname)
# Unbind from previous prop, if we had one.
if prev_prop is not None:
prev_prop.unbind(**bindings)
# Call all the bindings, to make sure things are updated.
for binding in bindings.values():
binding()
# Bind to new property's properties, if we have a new property.
if prop is not None:
prop.bind(**bindings)
# Call all the bindings, to make sure things are updated.
for binding in bindings.values():
binding()
# Set the previous property.
setattr(self, '_prev_' + propname, prop)
rebinder(self)
self.bind(**{propname: rebinder})
def _update_start(self, *ar):
start = self.start
if start is None:
self.start_pos = [0, 0]
else:
self.start_pos[0] = start.right
self.start_pos[1] = start.y + start.height / 2
def _update_end(self, *ar):
end = self.end
if end is None:
self.end_pos = [0, 0]
else:
x, y = self.end.to_window(*self.end.pos)
# print('window', x, y)
x, y = self.to_widget(x, y)
self.end_pos[0] = x
self.end_pos[1] = y + end.height / 2
Builder.load_string("""
<Arrow>:
canvas:
Color:
rgba: self.color if self.end is not None else (1, 1, 1, 0)
Line:
points: self.start_pos[0], self.start_pos[1], self.end_pos[0], self.end_pos[1]
width: 2
Color:
rgba: self.start_color if self.end is not None else (1, 1, 1, 0)
Line:
circle: self.start_pos[0], self.start_pos[1], 3
width: 2
Color:
rgba: self.end_color if self.end is not None else (1, 1, 1, 0)
Line:
circle: self.end_pos[0], self.end_pos[1], 5
width: 2
""")
if __name__ == '__main__':
from kivy.app import runTouchApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
b = BoxLayout(orientation='horizontal')
w1 = Label(text='start')
w2 = Widget()
w3 = Label(text='end')
a = Arrow(
start=w1,
end=w3
)
w1.add_widget(a)
b.add_widget(w1)
b.add_widget(w2)
b.add_widget(w3)
runTouchApp(b)