Phil Hung
Phil Hung

Reputation: 1

Question for Drag to Move and Resize Rect, Kivy

I am totally new to Kivy, I google many articles for "floating rectangles", I need this rectangles

enter image description here

I test "Scatter" layout, but it seems that "re-scale" behaviour not easy to use by Mouse click.

so, I would like to see any example(s) similar with I mentioned above. many thanks in advance!!!!

Upvotes: 0

Views: 937

Answers (2)

Core taxxe
Core taxxe

Reputation: 319

Luckily I faced the exact same problem. Here is the code to do so. It's actually self-explanatory + interesting lines are commented. Of course, you can ask me any question! Note: If you want to see changes in realtime move the code from on_touch_up to on_touch_move (at least most parts of it).

from kivy.core.window import Window

Window.clearcolor = (1, 1, 1, 1)
Window.size = (500, 500)
from kivy.uix.scatter import Scatter
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.uix.scatterlayout import ScatterLayout
from kivy.graphics.transformation import Matrix
from kivy.graphics import Color, Line


class BaseScatter(ScatterLayout):
    def __init__(self, **kwargs):
        super(BaseScatter, self).__init__(**kwargs)
        self.corner = None
        self.start_pos = [0, 0]


    def on_kv_post(self, base_widget):
        # makes rect visible
        with self.canvas.before:
            Color(0, 0, 0, 1)
            self.l = Line(rectangle=(0, 0, self.width, self.height))

    def on_touch_down(self, touch):
        x, y = self.to_local(*touch.pos)
        self.start_pos = x, y

        # check which corner is dragged
        # 20 is basically the width/height if the draggable area
        if 0 <= x <= 20 and 0 <= y <= 20:
            self.corner = "bottomleft"

        elif 0 <= x <= 20 and self.height - 20 <= y <= self.height:
            self.corner = 'topleft'

        elif self.width - 20 <= x <= self.width and self.height - 20 <= y <= self.height:
            self.corner = 'topright'

        elif self.width - 20 <= x <= self.width and 0 <= y <= 20:
            self.corner = 'bottomright'

        else:
            self.corner = 'drag'

    def on_touch_up(self, touch):
        # transform touch to local space
        x, y = self.to_local(*touch.pos)

        # calc mouse rel
        relx = self.start_pos[0] - x
        rely = self.start_pos[1] - y

        # resize depending on the corner pressed
        # this does not work with rotation yet since the rel value does not take that into account
        # ill update that maybe soon
        if self.corner == 'bottomleft':

            # apply size changes
            self.size[0] += relx
            self.size[1] += rely

            # repos widget (since size changes increase in x and y direction)
            self.x -= relx
            self.y -= rely

        elif self.corner == 'topleft':
            # apply size changes
            self.size[0] += relx
            self.size[1] -= rely  # reverse y value

            # repos widget (since size changes increase in x and y direction)
            self.x -= relx
            # y is not needed since we're not moving downwards

        elif self.corner == 'topright':
            # apply size changes
            # we need to reverse both
            self.size[0] -= relx
            self.size[1] -= rely

            # repos widget (since size changes increase in x and y direction)
            # we don't need that since we expand our rect in the xy direction

        elif self.corner == 'bottomright':
            # apply size changes
            self.size[0] -= relx # reverse x
            self.size[1] += rely

            # repos widget (since size changes increase in x and y direction)
            # we don't need x
            self.y -= rely

        else:
            # here goes the drag and drop
            self.x -= relx
            self.y -= rely


        # update rectangle
        self.l.rectangle = (0, 0, self.width, self.height)
        # reset self.corner
        self.corner = None


class TestApp(App):
    def build(self):
        root = FloatLayout()

        # i suppose this should be done separately
        base = BaseScatter(size=(200, 200), size_hint=(None, None), pos=(50, 50))
        lbl = Label(text="Hello!", color=(0, 0, 0, 1))

        base.add_widget(lbl)

        root.add_widget(base)

        return root


TestApp().run()


BaseScatter is like the parent rectangle while Label is the child that does nothing but displaying its content. I hope I didn't misunderstand your question and this helps you out!

Edit: The scatter may break by this approach the values should stay correct never the less

Upvotes: 0

John Anderson
John Anderson

Reputation: 39072

Here is a behavior class that can be used in place of DragBehavior to provide both drag and resize:

"""
DragAndResize Behavior
=============

The :class:`~dragandresize.DragAndResize`
`mixin <https://en.wikipedia.org/wiki/Mixin>`_ class provides Drag and Resize behavior.
When combined with a widget, dragging in the rectangle defined by the
:attr:`~kivy.uix.behaviors.drag.DragBehavior.drag_rectangle` will drag the
widget, and dragging in the border will resize the widget

Example
-------

The following example creates a draggable and resizable label::

    from kivy.uix.label import Label
    from kivy.app import App
    from kivy.lang import Builder
    from dragandresize import DragAndResize

    # You could also put the following in your kv file...
    kv = '''
    <DragAndResizeLabel>:
        # Define the properties for the DragLabel
        drag_rectangle: self.x, self.y, self.width, self.height
        drag_timeout: 10000000
        drag_distance: 0
        draw_resize_border: True
        resize_border_width: 5
        resize_border_color: [0,1,0,1]


    FloatLayout:
        # Define the root widget
        DragAndResizeLabel:
            size_hint: 0.25, 0.2
            text: 'Drag me'
    '''


    class DragAndResizeLabel(DragAndResize, Label):
        pass


    class TestApp(App):
        def build(self):
            return Builder.load_string(kv)

    TestApp().run()

"""

__all__ = ('DragAndResize', )

from kivy.core.window import Window
from kivy.lang import Builder
from kivy.properties import NumericProperty, BooleanProperty, ListProperty
from kivy.uix.behaviors import DragBehavior

Builder.load_string('''
<DragAndResize>:
    canvas.after:
        Color:
            rgba: self.resize_border_color
        Line:
            cap: 'square'
            joint: 'miter'
            width: self.resize_border_width
            rectangle: \
                (self.x + self.resize_border_width, self.y + self.resize_border_width,\
                self.width - 2 * self.resize_border_width, self.height - 2 * self.resize_border_width)\
                if self.draw_resize_border else (0,0,0,0)
''')


class DragAndResize(DragBehavior):
    resize_border_width = NumericProperty(5)
    '''
    width of the lines used to draw thw border. Note that the border width is actually twice this value.
    '''

    draw_resize_border = BooleanProperty(True)
    '''
    if True, the border will be drawn
    '''

    resize_border_color = ListProperty([1, 0, 0, 1])
    '''
    The color of the drawn border
    '''

    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            return super(DragAndResize, self).on_touch_up(touch)

        delta = self.resize_border_width * 2  # the drawn border is actually twice the specified width

        if touch.button == 'left':
            edit_type = 'pos'
            xx, yy = self.to_widget(*touch.pos, relative=True)
            if self.height - yy < delta:
                edit_type = 'top'
                if self.width - xx < delta:
                    edit_type = 'ne'
                    Window.set_system_cursor('crosshair')
                elif xx < delta:
                    edit_type = 'nw'
                    Window.set_system_cursor('crosshair')
                else:
                    Window.set_system_cursor('size_ns')
            elif yy < delta:
                edit_type = 'bottom'
                if self.width - xx < delta:
                    edit_type = 'se'
                    Window.set_system_cursor('crosshair')
                elif xx < delta:
                    edit_type = 'sw'
                    Window.set_system_cursor('crosshair')
                else:
                    Window.set_system_cursor('size_ns')
            elif self.width - xx < delta:
                edit_type = 'right'
                Window.set_system_cursor('size_we')
            elif xx < delta:
                edit_type = 'left'
                Window.set_system_cursor('size_we')
            else:
                Window.set_system_cursor('crosshair')
            touch.ud['edit_type'] = edit_type
            if edit_type != 'pos':
                touch.ud['size_node'] = self
                return True
        return super(DragAndResize, self).on_touch_down(touch)

    def do_top_size(self, xx, yy):
        if yy > 0:
            if self.size_hint_y is None:
                self.height = yy
            else:
                self.size_hint_y = yy / self.parent.height

    def do_bottom_size(self, xx, yy):
        if self.height - yy > 0:
            if self.size_hint_y is None:
                self.height -= yy
                self.y += yy
            else:
                self.size_hint_y = (self.height - yy) / self.parent.height
                self.y += yy

    def do_left_size(self, xx, yy):
        if self.width - xx > 0:
            if self.size_hint_x is None:
                self.width -= xx
                self.x += xx
            else:
                self.size_hint_x = (self.width - xx) / self.parent.width
                self.x += xx

    def do_right_size(self, xx, yy):
        if xx > 0:
            if self.size_hint_x is None:
                self.width = xx
            else:
                self.size_hint_x = xx / self.parent.width

    def on_touch_move(self, touch):
        if 'size_node' in touch.ud.keys():
            if touch.ud['size_node'] == self:
                xx, yy = self.to_widget(*touch.pos, relative=True)
                if touch.ud['edit_type'] == 'top':
                    self.do_top_size(xx, yy)
                elif touch.ud['edit_type'] == 'right':
                    self.do_right_size(xx, yy)
                elif touch.ud['edit_type'] == 'bottom':
                    self.do_bottom_size(xx, yy)
                elif touch.ud['edit_type'] == 'left':
                    self.do_left_size(xx, yy)
                elif touch.ud['edit_type'] == 'ne':
                    self.do_top_size(xx, yy)
                    self.do_right_size(xx, yy)
                elif touch.ud['edit_type'] == 'se':
                    self.do_bottom_size(xx, yy)
                    self.do_right_size(xx, yy)
                elif touch.ud['edit_type'] == 'sw':
                    self.do_bottom_size(xx, yy)
                    self.do_left_size(xx, yy)
                elif touch.ud['edit_type'] == 'nw':
                    self.do_top_size(xx, yy)
                    self.do_left_size(xx, yy)
                return True
        return super(DragAndResize, self).on_touch_move(touch)

    def on_touch_up(self, touch):
        Window.set_system_cursor('arrow')
        return super(DragAndResize, self).on_touch_up(touch)

Upvotes: 1

Related Questions