From 44730c8c03ec9207b2f1f6228acb0e391497246d Mon Sep 17 00:00:00 2001 From: Javier Sancho Date: Tue, 24 Jul 2018 16:53:57 +0200 Subject: [PATCH] Reserve space for widget when it's moving --- kivyforms/formcanvas.py | 223 +++++++++++++++++++++++++--------------- 1 file changed, 140 insertions(+), 83 deletions(-) diff --git a/kivyforms/formcanvas.py b/kivyforms/formcanvas.py index 0e233fb..8013ef6 100644 --- a/kivyforms/formcanvas.py +++ b/kivyforms/formcanvas.py @@ -2,117 +2,112 @@ from kivy.graphics import Color, Line 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): @@ -136,6 +131,68 @@ 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', -- 2.39.2