]> git.jsancho.org Git - kivyforms.git/blob - kivyforms/formcanvas.py
8013ef6d442d6ee38f2f3fa0fe86fc568941b523
[kivyforms.git] / kivyforms / formcanvas.py
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
7
8
9 """
10 Some common methods
11 """
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
18     return parent
19
20 def get_index(widget):
21     for idx in range(len(widget.parent.children)):
22         if widget.parent.children[idx] is widget:
23             return idx
24     return None
25
26 def get_zone(widget, x, y):
27     side = widget.x + widget.width / 4
28     if x > side * 3:
29         return 'right'
30     elif x < side:
31         return 'left'
32     elif y > widget.y + widget.height / 2:
33         return 'top'
34     else:
35         return 'bottom'
36
37
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:
44             Color(1, 1, 0, 1)
45             self.box = Line(dash_length=8, dash_offset=4)
46
47     def update_box(self):
48         self.box.points = [
49             self.x, self.y,
50             self.x, self.y + self.height,
51             self.x + self.width, self.y + self.height,
52             self.x + self.width, self.y,
53             self.x, self.y
54         ]
55
56     def on_pos(self, instance, value):
57         self.update_box()
58
59     def on_size(self, instance, value):
60         self.update_box()
61
62
63 class Grabbable(BoxLayout):
64     def __init__(self, **kwargs):
65         super(Grabbable, self).__init__(**kwargs)
66         self.detached = False
67
68     def point(self, touch):
69         get_form_canvas(self).point_widget(self, touch.pos)
70
71     def detach(self):
72         self.detached = True
73         get_form_canvas(self).detach_widget(self)
74
75     def attach(self, touch):
76         if self.detached:
77             form_canvas = None
78             for widget in self.get_root_window().children[1].walk():
79                 if isinstance(widget, FormCanvas) and widget.collide_point(*touch.pos):
80                     form_canvas = widget
81                     break
82             else:
83                 raise Exception("FormCanvas not found")
84             form_canvas.attach_widget(self)
85             self.detached = False
86
87     def move(self, x, y):
88         self.pos = [x - self.width / 2, y - self.height / 2]
89         
90     def on_touch_down(self, touch):
91         if self.collide_point(*touch.pos):
92             if touch.grab_current is None:
93                 touch.grab(self)
94                 self.detach()
95                 self.move(*touch.pos)
96                 return True
97         return super(Grabbable, self).on_touch_down(touch)
98
99     def on_touch_move(self, touch):
100         if touch.grab_current is self:
101             self.move(*touch.pos)
102         else:
103             if self.parent and self.parent != self.get_root_window():
104                 if self.collide_point(touch.x, touch.y):
105                     self.point(touch)
106
107     def on_touch_up(self, touch):
108         if touch.grab_current is self:
109             touch.ungrab(self)
110             self.attach(touch)
111             
112
113 class FormCanvas(ButtonBehavior, StackLayout):
114     def __init__(self, *args, **kwargs):
115         super(FormCanvas, self).__init__(*args, **kwargs)
116         self.n = 0
117         self.orientation = 'lr-tb'
118         self.padding = [10, 10, 10, 10]
119         self.spacing = [10, 10]
120
121         self.widgets_height = 40
122         self.widgets_size_hint = (1, None)
123
124     def on_press(self, *args):
125         self.n += 1
126         g = Grabbable(
127             height=self.widgets_height,
128             size_hint=self.widgets_size_hint
129         )
130         g.add_widget(Button(text=str(self.n)))
131         self.add_widget(g)
132         #print(self.export_to_kv(self))
133
134     def detach_widget(self, widget):
135         "Detach grabbable widget from canvas and show widget destination"
136
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))
142
143         # Place widget in the root window
144         widget.parent.remove_widget(widget)
145         widget.size_hint = (None, None)
146         widget.width = 150
147         widget.height = 50
148         self.get_root_window().add_widget(widget)
149
150     def point_widget(self, widget, position):
151         if not self.destination:
152             raise Exception("Wrong status: destination point not specified")
153
154         # Widget info about destination
155         widget_idx = get_index(widget)
156         zone = get_zone(widget, *position)
157
158         # Remove destination
159         box = self.destination.parent
160         box.remove_widget(self.destination)
161
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)
169             if zone == 'left':
170                 box.add_widget(self.destination, index=1)
171             else:
172                 box.add_widget(self.destination, index=0)
173         else:
174             widget.parent.add_widget(self.destination, index=widget_idx)
175
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)
182
183     def attach_widget(self, widget):
184         """
185         Attach grabbable widget to the destination
186         """
187         widget.parent.remove_widget(widget)
188         widget.height = self.widgets_height
189         widget.size_hint = self.widgets_size_hint
190
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
195
196     def create_box(self):
197         return BoxLayout(
198             orientation='horizontal',
199             height=self.widgets_height,
200             size_hint=self.widgets_size_hint,
201             spacing=self.spacing[0]
202         )
203
204     def export_to_kv(self, widget, indent=''):
205         kv = """StackLayout:
206     orientation: '{orientation}'
207     padding: {padding}
208     spacing: {spacing}
209 """.format(orientation=self.orientation, padding=self.padding, spacing=self.spacing)
210
211         indent = '    '
212         stack = [self]
213
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]:
223                     stack.pop()
224
225                 # Widget header
226                 kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
227
228                 stack.append(widget)
229
230                 # Widget attributes
231                 kv += """{indent}height: 40
232 {indent}size_hint: (1., None)
233 {indent}text: '{text}'
234 """.format(indent=indent*len(stack), text=widget.text)
235
236         return kv