Initial commit
authorJavier Sancho <jsf@jsancho.org>
Wed, 18 Jul 2018 10:13:46 +0000 (12:13 +0200)
committerJavier Sancho <jsf@jsancho.org>
Wed, 18 Jul 2018 10:13:46 +0000 (12:13 +0200)
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
VERSION [new file with mode: 0644]
examples/designer.py [new file with mode: 0644]
kivyforms/__init__.py [new file with mode: 0644]
kivyforms/formcanvas.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..ba3ae94
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2018 Javier Sancho
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..49d5957
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1
diff --git a/examples/designer.py b/examples/designer.py
new file mode 100644 (file)
index 0000000..5ccdb50
--- /dev/null
@@ -0,0 +1,10 @@
+from kivy.app import App
+from kivyforms import FormCanvas
+
+class DesignerApp(App):
+    def build(self):
+        return FormCanvas()
+
+if __name__ == '__main__':
+    app = DesignerApp()
+    app.run()
diff --git a/kivyforms/__init__.py b/kivyforms/__init__.py
new file mode 100644 (file)
index 0000000..26a0a18
--- /dev/null
@@ -0,0 +1 @@
+from .formcanvas import FormCanvas
diff --git a/kivyforms/formcanvas.py b/kivyforms/formcanvas.py
new file mode 100644 (file)
index 0000000..0f8f2fe
--- /dev/null
@@ -0,0 +1,180 @@
+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.stacklayout import StackLayout
+
+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]
+
+        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 type(widget) is 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)
+        
+    def on_touch_down(self, touch):
+        if self.collide_point(*touch.pos):
+            if touch.grab_current is None:
+                touch.grab(self)
+                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()
+        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)
+            
+
+class FormCanvas(ButtonBehavior, StackLayout):
+    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.widgets_height = 40
+        self.widgets_size_hint = (1, None)
+
+    def on_press(self, *args):
+        self.n += 1
+        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))
+
+    def create_box(self):
+        return BoxLayout(
+            orientation='horizontal',
+            height=self.widgets_height,
+            size_hint=self.widgets_size_hint,
+            spacing=self.spacing[0]
+        )
+
+    def export_to_kv(self, widget, indent=''):
+        kv = """StackLayout:
+    orientation: '{orientation}'
+    padding: {padding}
+    spacing: {spacing}
+""".format(orientation=self.orientation, padding=self.padding, spacing=self.spacing)
+
+        indent = '    '
+        stack = [self]
+
+        widgets = self.walk(restrict=True)
+        next(widgets)    # the first widget is the FormCanvas
+        for widget in widgets:
+            if not type(widget) is Grabbable:
+                # Look for the widget position inside the tree
+                parent = widget.parent
+                if type(parent) is Grabbable:
+                    parent = parent.parent
+                while not parent is stack[-1]:
+                    stack.pop()
+
+                # Widget header
+                kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
+
+                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)
+
+        return kv
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..0387653
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,26 @@
+from setuptools import setup
+import os
+
+def get_version():
+    with open('VERSION') as fd:
+        return fd.read().strip()
+
+def get_long_description():
+    with open(os.path.join(
+        os.path.dirname(os.path.abspath(__file__)), 'README.md'
+    ), encoding='utf8') as fp:
+        return fp.read()
+
+
+setup(
+    name='kivyforms',
+    version=get_version(),
+    description='Tools for making forms easy',
+    long_description=get_long_description(),
+    long_description_content_type='text/markdown',
+    author='Javier Sancho',
+    url='https://git.jsancho.org/kivyforms',
+    license='MIT',
+    packages=['kivyforms'],
+    install_requires=['kivy']
+)