from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
+from kivy.uix.label import Label
from kivy.uix.stacklayout import StackLayout
+
+"""
+Some common methods
+"""
+def get_form_canvas(widget):
+ parent = widget.parent
+ while parent and not isinstance(parent, FormCanvas):
+ if parent is parent.parent:
+ raise Exception("Infinite loop when searching FormCanvas")
+ parent = parent.parent
+ return parent
+
+def get_index(widget):
+ for idx in range(len(widget.parent.children)):
+ if widget.parent.children[idx] is widget:
+ return idx
+ return None
+
+def get_zone(widget, x, y):
+ side = widget.x + widget.width / 4
+ if x > side * 3:
+ return 'right'
+ elif x < side:
+ return 'left'
+ elif y > widget.y + widget.height / 2:
+ return 'top'
+ else:
+ return 'bottom'
+
+
+class Destination(Label):
+ def __init__(self, **kwargs):
+ super(Destination, self).__init__(**kwargs)
+ self.text = 'Widget goes here'
+ self.color = [1, 1, 0, 1]
+ with self.canvas.after:
+ Color(1, 1, 0, 1)
+ self.box = Line(dash_length=8, dash_offset=4)
+
+ def update_box(self):
+ self.box.points = [
+ self.x, self.y,
+ self.x, self.y + self.height,
+ self.x + self.width, self.y + self.height,
+ self.x + self.width, self.y,
+ self.x, self.y
+ ]
+
+ def on_pos(self, instance, value):
+ self.update_box()
+
+ def on_size(self, instance, value):
+ self.update_box()
+
+
class Grabbable(BoxLayout):
def __init__(self, **kwargs):
super(Grabbable, self).__init__(**kwargs)
self.detached = False
- self.form_canvas = None
- self.point_area = None
- with self.canvas.after:
- Color(1, 1, 0)
- self.line = Line(points=[])
-
- def point(self, touch=None):
- if touch:
- side = self.x + self.width / 6
- if touch.x > side * 5:
- self.point_area = 'right'
- self.line.points = [self.x + self.width + 5, self.y + 5, self.x + self.width + 5, self.y + self.height - 5]
- elif touch.x < side:
- self.point_area = 'left'
- self.line.points = [self.x - 5, self.y + 5, self.x - 5, self.y + self.height - 5]
- elif touch.y > self.y + self.height / 2:
- self.point_area = 'top'
- self.line.points = [self.x + 5, self.y + self.height + 5, self.x + self.width - 5, self.y + self.height + 5]
- else:
- self.point_area = 'bottom'
- self.line.points = [self.x + 5, self.y - 5, self.x + self.width - 5, self.y - 5]
+ def point(self, touch):
+ get_form_canvas(self).point_widget(self, touch.pos)
- else:
- self.point_area = None
- self.line.points = []
-
- def get_form_canvas(self):
- parent = self.parent
- while parent and not isinstance(parent, FormCanvas):
- parent = parent.parent
- return parent
-
- def get_index(self):
- for idx in range(len(self.parent.children)):
- if self.parent.children[idx] is self:
- return idx
- return None
-
def detach(self):
self.detached = True
- self.form_canvas = self.get_form_canvas()
- root_window = self.get_root_window()
- self.parent.remove_widget(self)
- self.size_hint = (None, None)
- self.width = 150
- self.height = 50
- root_window.add_widget(self)
-
- def attach(self, x, y):
- self.detached = False
- self.parent.remove_widget(self)
- self.height = self.form_canvas.widgets_height
- self.size_hint = self.form_canvas.widgets_size_hint
-
- for widget in self.form_canvas.walk(restrict=True):
- if isinstance(widget, Grabbable) and widget.collide_point(x, y):
- idx = widget.get_index()
- if widget.point_area == 'top':
- widget.parent.add_widget(self, index=idx + 1)
- elif widget.point_area == 'bottom':
- widget.parent.add_widget(self, index=idx)
- else:
- parent = widget.parent
- if not isinstance(parent, BoxLayout):
- box = parent.create_box()
- parent.add_widget(box, index=idx)
- parent.remove_widget(widget)
- box.add_widget(widget)
- idx = 0
- parent = box
- if widget.point_area == 'left':
- parent.add_widget(self, index=idx + 1)
- else:
- parent.add_widget(self, index=idx)
- widget.point()
- break
- else:
- self.form_canvas.add_widget(self)
+ get_form_canvas(self).detach_widget(self)
+
+ def attach(self, touch):
+ if self.detached:
+ form_canvas = None
+ for widget in self.get_root_window().children[1].walk():
+ if isinstance(widget, FormCanvas) and widget.collide_point(*touch.pos):
+ form_canvas = widget
+ break
+ else:
+ raise Exception("FormCanvas not found")
+ form_canvas.attach_widget(self)
+ self.detached = False
+
+ def move(self, x, y):
+ self.pos = [x - self.width / 2, y - self.height / 2]
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.grab_current is None:
touch.grab(self)
+ self.detach()
+ self.move(*touch.pos)
return True
return super(Grabbable, self).on_touch_down(touch)
def on_touch_move(self, touch):
if touch.grab_current is self:
- self.pos = [touch.x - self.width / 2, touch.y - self.height / 2]
- self.point()
- if not self.detached:
- self.detach()
+ self.move(*touch.pos)
else:
if self.parent and self.parent != self.get_root_window():
if self.collide_point(touch.x, touch.y):
self.point(touch)
- else:
- self.point()
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self)
- if self.detached:
- self.attach(touch.x, touch.y)
+ self.attach(touch)
class FormCanvas(ButtonBehavior, StackLayout):
self.add_widget(g)
#print(self.export_to_kv(self))
+ def detach_widget(self, widget):
+ "Detach grabbable widget from canvas and show widget destination"
+
+ # Show widget destination
+ self.destination = Destination()
+ self.destination.height = self.widgets_height
+ self.destination.size_hint = self.widgets_size_hint
+ widget.parent.add_widget(self.destination, index=get_index(widget))
+
+ # Place widget in the root window
+ widget.parent.remove_widget(widget)
+ widget.size_hint = (None, None)
+ widget.width = 150
+ widget.height = 50
+ self.get_root_window().add_widget(widget)
+
+ def point_widget(self, widget, position):
+ if not self.destination:
+ raise Exception("Wrong status: destination point not specified")
+
+ # Widget info about destination
+ widget_idx = get_index(widget)
+ zone = get_zone(widget, *position)
+
+ # Remove destination
+ box = self.destination.parent
+ box.remove_widget(self.destination)
+
+ # Put destination in the right place
+ if zone in ('left', 'right') and widget.parent.orientation != 'horizontal':
+ parent = widget.parent
+ box = parent.create_box()
+ parent.add_widget(box, index=widget_idx)
+ parent.remove_widget(widget)
+ box.add_widget(widget)
+ if zone == 'left':
+ box.add_widget(self.destination, index=1)
+ else:
+ box.add_widget(self.destination, index=0)
+ else:
+ widget.parent.add_widget(self.destination, index=widget_idx)
+
+ # Remove useless boxes
+ if isinstance(box, BoxLayout) and len(box.children) == 1:
+ child = box.children[0]
+ box.remove_widget(child)
+ box.parent.add_widget(child, index=get_index(box))
+ box.parent.remove_widget(box)
+
+ def attach_widget(self, widget):
+ """
+ Attach grabbable widget to the destination
+ """
+ widget.parent.remove_widget(widget)
+ widget.height = self.widgets_height
+ widget.size_hint = self.widgets_size_hint
+
+ dest_idx = get_index(self.destination)
+ self.destination.parent.add_widget(widget, index=dest_idx)
+ self.destination.parent.remove_widget(self.destination)
+ self.destination = None
+
def create_box(self):
return BoxLayout(
orientation='horizontal',