1 from kivy.graphics import Color, Line
2 from kivy.uix.behaviors import ButtonBehavior
3 from kivy.uix.boxlayout import BoxLayout
4 from kivy.uix.button import Button
5 from kivy.uix.label import Label
6 from kivy.uix.stacklayout import StackLayout
12 def get_form_canvas(widget):
13 parent = widget.parent
14 while parent and not isinstance(parent, FormCanvas):
15 if parent is parent.parent:
16 raise Exception("Infinite loop when searching FormCanvas")
17 parent = parent.parent
20 def get_index(widget):
21 for idx in range(len(widget.parent.children)):
22 if widget.parent.children[idx] is widget:
26 def get_zone(widget, x, y):
27 side = widget.x + widget.width / 4
32 elif y > widget.y + widget.height / 2:
38 class Destination(Label):
39 def __init__(self, **kwargs):
40 super(Destination, self).__init__(**kwargs)
41 self.text = 'Widget goes here'
42 self.color = [1, 1, 0, 1]
43 with self.canvas.after:
45 self.box = Line(dash_length=8, dash_offset=4)
50 self.x, self.y + self.height,
51 self.x + self.width, self.y + self.height,
52 self.x + self.width, self.y,
56 def on_pos(self, instance, value):
59 def on_size(self, instance, value):
63 class Grabbable(BoxLayout):
64 def __init__(self, **kwargs):
65 super(Grabbable, self).__init__(**kwargs)
68 def point(self, touch):
69 get_form_canvas(self).point_widget(self, touch.pos)
73 get_form_canvas(self).detach_widget(self)
75 def attach(self, touch):
78 for widget in self.get_root_window().children[1].walk():
79 if isinstance(widget, FormCanvas) and widget.collide_point(*touch.pos):
83 raise Exception("FormCanvas not found")
84 form_canvas.attach_widget(self)
88 self.pos = [x - self.width / 2, y - self.height / 2]
90 def on_touch_down(self, touch):
91 if self.collide_point(*touch.pos):
92 if touch.grab_current is None:
97 return super(Grabbable, self).on_touch_down(touch)
99 def on_touch_move(self, touch):
100 if touch.grab_current is self:
101 self.move(*touch.pos)
103 if self.parent and self.parent != self.get_root_window():
104 if self.collide_point(touch.x, touch.y):
107 def on_touch_up(self, touch):
108 if touch.grab_current is self:
113 class FormCanvas(ButtonBehavior, StackLayout):
114 def __init__(self, *args, **kwargs):
115 super(FormCanvas, self).__init__(*args, **kwargs)
117 self.orientation = 'lr-tb'
118 self.padding = [10, 10, 10, 10]
119 self.spacing = [10, 10]
121 self.widgets_height = 40
122 self.widgets_size_hint = (1, None)
124 def on_press(self, *args):
127 height=self.widgets_height,
128 size_hint=self.widgets_size_hint
130 g.add_widget(Button(text=str(self.n)))
132 #print(self.export_to_kv(self))
134 def detach_widget(self, widget):
135 "Detach grabbable widget from canvas and show widget destination"
137 # Show widget destination
138 self.destination = Destination()
139 self.destination.height = self.widgets_height
140 self.destination.size_hint = self.widgets_size_hint
141 widget.parent.add_widget(self.destination, index=get_index(widget))
143 # Place widget in the root window
144 widget.parent.remove_widget(widget)
145 widget.size_hint = (None, None)
148 self.get_root_window().add_widget(widget)
150 def point_widget(self, widget, position):
151 if not self.destination:
152 raise Exception("Wrong status: destination point not specified")
154 # Widget info about destination
155 widget_idx = get_index(widget)
156 zone = get_zone(widget, *position)
159 box = self.destination.parent
160 box.remove_widget(self.destination)
162 # Put destination in the right place
163 if zone in ('left', 'right') and widget.parent.orientation != 'horizontal':
164 parent = widget.parent
165 box = parent.create_box()
166 parent.add_widget(box, index=widget_idx)
167 parent.remove_widget(widget)
168 box.add_widget(widget)
170 box.add_widget(self.destination, index=1)
172 box.add_widget(self.destination, index=0)
174 widget.parent.add_widget(self.destination, index=widget_idx)
176 # Remove useless boxes
177 if isinstance(box, BoxLayout) and len(box.children) == 1:
178 child = box.children[0]
179 box.remove_widget(child)
180 box.parent.add_widget(child, index=get_index(box))
181 box.parent.remove_widget(box)
183 def attach_widget(self, widget):
185 Attach grabbable widget to the destination
187 widget.parent.remove_widget(widget)
188 widget.height = self.widgets_height
189 widget.size_hint = self.widgets_size_hint
191 dest_idx = get_index(self.destination)
192 self.destination.parent.add_widget(widget, index=dest_idx)
193 self.destination.parent.remove_widget(self.destination)
194 self.destination = None
196 def create_box(self):
198 orientation='horizontal',
199 height=self.widgets_height,
200 size_hint=self.widgets_size_hint,
201 spacing=self.spacing[0]
204 def export_to_kv(self, widget, indent=''):
206 orientation: '{orientation}'
209 """.format(orientation=self.orientation, padding=self.padding, spacing=self.spacing)
214 widgets = self.walk(restrict=True)
215 next(widgets) # the first widget is the FormCanvas
216 for widget in widgets:
217 if not isinstance(widget, Grabbable):
218 # Look for the widget position inside the tree
219 parent = widget.parent
220 if isinstance(parent, Grabbable):
221 parent = parent.parent
222 while not parent is stack[-1]:
226 kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
231 kv += """{indent}height: 40
232 {indent}size_hint: (1., None)
233 {indent}text: '{text}'
234 """.format(indent=indent*len(stack), text=widget.text)