X-Git-Url: https://git.jsancho.org/?p=kivyforms.git;a=blobdiff_plain;f=kivyforms%2Fformcanvas.py;h=3c6ab6003aba2f38bdf61d54c302260307e65721;hp=4cd1b802f55db19210d226a2ca2952d111123575;hb=0aa8dfced2da3c8255a6c6da9c3d9cc9c9e5566b;hpb=b3f59c08b88ab111e72ea08bedf6c0af0e86f294 diff --git a/kivyforms/formcanvas.py b/kivyforms/formcanvas.py index 4cd1b80..3c6ab60 100644 --- a/kivyforms/formcanvas.py +++ b/kivyforms/formcanvas.py @@ -1,162 +1,221 @@ 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) - elif widget.point_area == 'left': - parent = widget.parent - box = parent.create_box() - parent.add_widget(box, index=idx) - box.add_widget(self) - parent.remove_widget(widget) - box.add_widget(widget) - else: - parent = widget.parent - box = parent.create_box() - parent.add_widget(box, index=idx) - parent.remove_widget(widget) - box.add_widget(widget) - box.add_widget(self) - 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): +class FormCanvas(BoxLayout): def __init__(self, *args, **kwargs): super(FormCanvas, self).__init__(*args, **kwargs) - self.n = 0 - self.orientation = 'lr-tb' - self.padding = [10, 10, 10, 10] - self.spacing = [10, 10] + + self._canvas = StackLayout( + orientation='lr-tb', + padding=[10, 10, 10, 10], + spacing=[10, 10] + ) + super(FormCanvas, self).add_widget(self._canvas) self.widgets_height = 40 self.widgets_size_hint = (1, None) - def on_press(self, *args): - self.n += 1 + def add_widget(self, widget): g = Grabbable( height=self.widgets_height, size_hint=self.widgets_size_hint ) - g.add_widget(Button(text=str(self.n))) - self.add_widget(g) - #print(self.export_to_kv(self)) + widget.height = self.widgets_height + widget.size_hint = self.widgets_size_hint + g.add_widget(widget) + self._canvas.add_widget(g) + + 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 = self.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', height=self.widgets_height, size_hint=self.widgets_size_hint, - spacing=self.spacing[0] + spacing=self._canvas.spacing[0] ) - def export_to_kv(self, widget, indent=''): + def export_to_kv(self): kv = """StackLayout: orientation: '{orientation}' padding: {padding} spacing: {spacing} -""".format(orientation=self.orientation, padding=self.padding, spacing=self.spacing) +""".format(orientation=self._canvas.orientation, padding=self._canvas.padding, spacing=self._canvas.spacing) indent = ' ' - stack = [self] + stack = [self._canvas] widgets = self.walk(restrict=True) next(widgets) # the first widget is the FormCanvas + next(widgets) # and the second is the inner StackLayout for widget in widgets: if not isinstance(widget, Grabbable): # Look for the widget position inside the tree @@ -172,9 +231,15 @@ class FormCanvas(ButtonBehavior, StackLayout): stack.append(widget) # Widget attributes - kv += """{indent}height: 40 -{indent}size_hint: (1., None) -{indent}text: '{text}' -""".format(indent=indent*len(stack), text=widget.text) + for attr in ('height', 'size_hint', 'text', 'spacing'): + if hasattr(widget, attr): + value = getattr(widget, attr) + if type(value) is str: + value = "'" + value + "'" + kv += "{indent}{attr}: {value}\n".format( + indent=indent*len(stack), + attr=attr, + value=value + ) return kv