1 from kivy.graphics import Color, Line
2 from kivy.uix.boxlayout import BoxLayout
3 from kivy.uix.button import Button
4 from kivy.uix.label import Label
5 from kivy.uix.stacklayout import StackLayout
11 def get_form_canvas(widget):
12 parent = widget.parent
13 while parent and not isinstance(parent, FormCanvas):
14 if parent is parent.parent:
15 raise Exception("Infinite loop when searching FormCanvas")
16 parent = parent.parent
19 def get_index(widget):
20 for idx in range(len(widget.parent.children)):
21 if widget.parent.children[idx] is widget:
25 def get_zone(widget, x, y):
26 side = widget.x + widget.width / 4
31 elif y > widget.y + widget.height / 2:
37 class Destination(Label):
38 def __init__(self, **kwargs):
39 super(Destination, self).__init__(**kwargs)
40 self.text = 'Widget goes here'
41 self.color = [1, 1, 0, 1]
42 with self.canvas.after:
44 self.box = Line(dash_length=8, dash_offset=4)
49 self.x, self.y + self.height,
50 self.x + self.width, self.y + self.height,
51 self.x + self.width, self.y,
55 def on_pos(self, instance, value):
58 def on_size(self, instance, value):
62 class Grabbable(BoxLayout):
63 def __init__(self, **kwargs):
64 super(Grabbable, self).__init__(**kwargs)
67 def point(self, touch):
68 get_form_canvas(self).point_widget(self, touch.pos)
72 get_form_canvas(self).detach_widget(self)
74 def attach(self, touch):
77 for widget in self.get_root_window().children[1].walk():
78 if isinstance(widget, FormCanvas) and widget.collide_point(*touch.pos):
82 raise Exception("FormCanvas not found")
83 form_canvas.attach_widget(self)
87 self.pos = [x - self.width / 2, y - self.height / 2]
89 def on_touch_down(self, touch):
90 if self.collide_point(*touch.pos):
91 if touch.grab_current is None:
96 return super(Grabbable, self).on_touch_down(touch)
98 def on_touch_move(self, touch):
99 if touch.grab_current is self:
100 self.move(*touch.pos)
102 if self.parent and self.parent != self.get_root_window():
103 if self.collide_point(touch.x, touch.y):
106 def on_touch_up(self, touch):
107 if touch.grab_current is self:
112 class FormCanvas(BoxLayout):
113 def __init__(self, *args, **kwargs):
114 super(FormCanvas, self).__init__(*args, **kwargs)
116 self._canvas = StackLayout(
118 padding=[10, 10, 10, 10],
121 super(FormCanvas, self).add_widget(self._canvas)
123 self.widgets_height = 40
124 self.widgets_size_hint = (1, None)
126 def add_widget(self, widget):
128 height=self.widgets_height,
129 size_hint=self.widgets_size_hint
131 widget.height = self.widgets_height
132 widget.size_hint = self.widgets_size_hint
134 self._canvas.add_widget(g)
136 def detach_widget(self, widget):
137 "Detach grabbable widget from canvas and show widget destination"
139 # Show widget destination
140 self.destination = Destination()
141 self.destination.height = self.widgets_height
142 self.destination.size_hint = self.widgets_size_hint
143 widget.parent.add_widget(self.destination, index=get_index(widget))
145 # Place widget in the root window
146 widget.parent.remove_widget(widget)
147 widget.size_hint = (None, None)
150 self.get_root_window().add_widget(widget)
152 def point_widget(self, widget, position):
153 if not self.destination:
154 raise Exception("Wrong status: destination point not specified")
156 # Widget info about destination
157 widget_idx = get_index(widget)
158 zone = get_zone(widget, *position)
161 box = self.destination.parent
162 box.remove_widget(self.destination)
164 # Put destination in the right place
165 if zone in ('left', 'right') and widget.parent.orientation != 'horizontal':
166 parent = widget.parent
167 box = self.create_box()
168 parent.add_widget(box, index=widget_idx)
169 parent.remove_widget(widget)
170 box.add_widget(widget)
172 box.add_widget(self.destination, index=1)
174 box.add_widget(self.destination, index=0)
176 widget.parent.add_widget(self.destination, index=widget_idx)
178 # Remove useless boxes
179 if isinstance(box, BoxLayout) and len(box.children) == 1:
180 child = box.children[0]
181 box.remove_widget(child)
182 box.parent.add_widget(child, index=get_index(box))
183 box.parent.remove_widget(box)
185 def attach_widget(self, widget):
187 Attach grabbable widget to the destination
189 widget.parent.remove_widget(widget)
190 widget.height = self.widgets_height
191 widget.size_hint = self.widgets_size_hint
193 dest_idx = get_index(self.destination)
194 self.destination.parent.add_widget(widget, index=dest_idx)
195 self.destination.parent.remove_widget(self.destination)
196 self.destination = None
198 def create_box(self):
200 orientation='horizontal',
201 height=self.widgets_height,
202 size_hint=self.widgets_size_hint,
203 spacing=self._canvas.spacing[0]
206 def export_to_kv(self):
208 orientation: '{orientation}'
211 """.format(orientation=self._canvas.orientation, padding=self._canvas.padding, spacing=self._canvas.spacing)
214 stack = [self._canvas]
216 widgets = self.walk(restrict=True)
217 next(widgets) # the first widget is the FormCanvas
218 next(widgets) # and the second is the inner StackLayout
219 for widget in widgets:
220 if not isinstance(widget, Grabbable):
221 # Look for the widget position inside the tree
222 parent = widget.parent
223 if isinstance(parent, Grabbable):
224 parent = parent.parent
225 while not parent is stack[-1]:
229 kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
234 for attr in ('height', 'size_hint', 'text', 'spacing'):
235 if hasattr(widget, attr):
236 value = getattr(widget, attr)
237 if type(value) is str:
238 value = "'" + value + "'"
239 kv += "{indent}{attr}: {value}\n".format(
240 indent=indent*len(stack),