]> git.jsancho.org Git - kivyforms.git/blob - kivyforms/formcanvas.py
Create boxes only when needed
[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.stacklayout import StackLayout
6
7 class Grabbable(BoxLayout):
8     def __init__(self, **kwargs):
9         super(Grabbable, self).__init__(**kwargs)
10         self.detached = False
11         self.form_canvas = None
12
13         self.point_area = None
14         with self.canvas.after:
15             Color(1, 1, 0)
16             self.line = Line(points=[])
17
18     def point(self, touch=None):
19         if touch:
20             side = self.x + self.width / 6
21             if touch.x > side * 5:
22                 self.point_area = 'right'
23                 self.line.points = [self.x + self.width + 5, self.y + 5, self.x + self.width + 5, self.y + self.height - 5]
24             elif touch.x < side:
25                 self.point_area = 'left'
26                 self.line.points = [self.x - 5, self.y + 5, self.x - 5, self.y + self.height - 5]
27             elif touch.y > self.y + self.height / 2:
28                 self.point_area = 'top'
29                 self.line.points = [self.x + 5, self.y + self.height + 5, self.x + self.width - 5, self.y + self.height + 5]
30             else:
31                 self.point_area = 'bottom'
32                 self.line.points = [self.x + 5, self.y - 5, self.x + self.width - 5, self.y - 5]
33
34         else:
35             self.point_area = None
36             self.line.points = []
37
38     def get_form_canvas(self):
39         parent = self.parent
40         while parent and not isinstance(parent, FormCanvas):
41             parent = parent.parent
42         return parent
43
44     def get_index(self):
45         for idx in range(len(self.parent.children)):
46             if self.parent.children[idx] is self:
47                 return idx
48         return None
49             
50     def detach(self):
51         self.detached = True
52         self.form_canvas = self.get_form_canvas()
53         root_window = self.get_root_window()
54         self.parent.remove_widget(self)
55         self.size_hint = (None, None)
56         self.width = 150
57         self.height = 50
58         root_window.add_widget(self)
59
60     def attach(self, x, y):
61         self.detached = False
62         self.parent.remove_widget(self)
63         self.height = self.form_canvas.widgets_height
64         self.size_hint = self.form_canvas.widgets_size_hint
65
66         for widget in self.form_canvas.walk(restrict=True):
67             if isinstance(widget, Grabbable) and widget.collide_point(x, y):
68                 idx = widget.get_index()
69                 if widget.point_area == 'top':
70                     widget.parent.add_widget(self, index=idx + 1)
71                 elif widget.point_area == 'bottom':
72                     widget.parent.add_widget(self, index=idx)
73                 else:
74                     parent = widget.parent
75                     if not isinstance(parent, BoxLayout):
76                         box = parent.create_box()
77                         parent.add_widget(box, index=idx)
78                         parent.remove_widget(widget)
79                         box.add_widget(widget)
80                         idx = 0
81                         parent = box
82                     if widget.point_area == 'left':
83                         parent.add_widget(self, index=idx + 1)
84                     else:
85                         parent.add_widget(self, index=idx)
86                 widget.point()
87                 break
88         else:
89             self.form_canvas.add_widget(self)
90         
91     def on_touch_down(self, touch):
92         if self.collide_point(*touch.pos):
93             if touch.grab_current is None:
94                 touch.grab(self)
95                 return True
96         return super(Grabbable, self).on_touch_down(touch)
97
98     def on_touch_move(self, touch):
99         if touch.grab_current is self:
100             self.pos = [touch.x - self.width / 2, touch.y - self.height / 2]
101             self.point()
102             if not self.detached:
103                 self.detach()
104         else:
105             if self.parent and self.parent != self.get_root_window():
106                 if self.collide_point(touch.x, touch.y):
107                     self.point(touch)
108                 else:
109                     self.point()
110
111     def on_touch_up(self, touch):
112         if touch.grab_current is self:
113             touch.ungrab(self)
114             if self.detached:
115                 self.attach(touch.x, touch.y)
116             
117
118 class FormCanvas(ButtonBehavior, StackLayout):
119     def __init__(self, *args, **kwargs):
120         super(FormCanvas, self).__init__(*args, **kwargs)
121         self.n = 0
122         self.orientation = 'lr-tb'
123         self.padding = [10, 10, 10, 10]
124         self.spacing = [10, 10]
125
126         self.widgets_height = 40
127         self.widgets_size_hint = (1, None)
128
129     def on_press(self, *args):
130         self.n += 1
131         g = Grabbable(
132             height=self.widgets_height,
133             size_hint=self.widgets_size_hint
134         )
135         g.add_widget(Button(text=str(self.n)))
136         self.add_widget(g)
137         #print(self.export_to_kv(self))
138
139     def create_box(self):
140         return BoxLayout(
141             orientation='horizontal',
142             height=self.widgets_height,
143             size_hint=self.widgets_size_hint,
144             spacing=self.spacing[0]
145         )
146
147     def export_to_kv(self, widget, indent=''):
148         kv = """StackLayout:
149     orientation: '{orientation}'
150     padding: {padding}
151     spacing: {spacing}
152 """.format(orientation=self.orientation, padding=self.padding, spacing=self.spacing)
153
154         indent = '    '
155         stack = [self]
156
157         widgets = self.walk(restrict=True)
158         next(widgets)    # the first widget is the FormCanvas
159         for widget in widgets:
160             if not isinstance(widget, Grabbable):
161                 # Look for the widget position inside the tree
162                 parent = widget.parent
163                 if isinstance(parent, Grabbable):
164                     parent = parent.parent
165                 while not parent is stack[-1]:
166                     stack.pop()
167
168                 # Widget header
169                 kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
170
171                 stack.append(widget)
172
173                 # Widget attributes
174                 kv += """{indent}height: 40
175 {indent}size_hint: (1., None)
176 {indent}text: '{text}'
177 """.format(indent=indent*len(stack), text=widget.text)
178
179         return kv