collide_widget() not working as expected

39 views
Skip to first unread message

GJG

unread,
Nov 12, 2020, 10:04:12 PM11/12/20
to kivy-...@googlegroups.com
I'm trying to implement drag selection like below:

I try to do this by creating a layout that inherits from CompoundSelectionBehavior and FloatLayout. This custom layout has a widget representing the blue selection box in the gif above. I tried to use collide_widget() to select other widgets, but it doesn't work as expected. Here's the code and a gif:
drag-select.gif
from kivy.app import App
from kivy.clock import Clock
from kivy.config import Config
from kivy.graphics import Color, Line, Rectangle
from kivy.lang import Builder
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.uix.behaviors.compoundselection import CompoundSelectionBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget

Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
kv = '''
<MyWidget>:
size_hint: 0.2, 0.2

MyLayout:
canvas:
Color:
rgba: 1, 1, 1, 0.07
Rectangle:
pos: self.pos
size: self.size

MyWidget:
pos_hint: {'center_x': 0.5, 'center_y': 0.8}

MyWidget:
pos_hint: {'center_x': 0.5, 'center_y': 0.5}

MyWidget:
pos_hint: {'center_x': 0.5, 'center_y': 0.2}
'''


class MyWidget(Widget):
bg_color = ObjectProperty(Color(1, 1, 1, 0.97))

def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.canvas.add(self.bg_color)
self.canvas.add(self.rect)
self.bind(pos=self.update_rect,
size=self.update_rect,
bg_color=self.redraw)

def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size

def redraw(self, *args):
self.canvas.clear()
self.canvas.add(self.bg_color)
self.canvas.add(self.rect)

def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.parent.select_with_touch(self, touch)
return True
else:
self.parent.deselect_node(self)
return super(MyWidget, self).on_touch_down(touch)


class DragSelection(Widget):
"""Widget that represents the highlight on a click and drag"""
bg_color = ObjectProperty(Color(0, 0, 1, 0.3))
bg_outline_color = ObjectProperty(Color(0, 0, 1, 0.5))

def __init__(self, **kwargs):
self._redraw = Clock.create_trigger(self.redraw)
super(DragSelection, self).__init__(**kwargs)
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect,
size=self.update_rect)

def update_rect(self, *args) -> None:
self.rect.pos = self.pos
self.rect.size = self.size
self._redraw()

def redraw(self, *args) -> None:
self.canvas.clear()
self.canvas.add(self.bg_color)
self.canvas.add(self.rect)
self.canvas.add(self.bg_outline_color)
self.canvas.add(Line(rectangle=[*self.rect.pos, *self.rect.size]))


class MyLayout(CompoundSelectionBehavior, FloatLayout):
drag_selecting = BooleanProperty(False)
dsw = ObjectProperty(DragSelection(size_hint=(None, None))) # drag selection widget

def __init__(self, **kwargs):
self._redraw = Clock.create_trigger(self.redraw)
super(MyLayout, self).__init__(**kwargs)

def select_node(self, node):
node.bg_color = Color(1, 0, 0, 1)
return super(MyLayout, self).select_node(node)

def deselect_node(self, node):
node.bg_color = Color(1, 1, 1, 0.98)
return super(MyLayout, self).deselect_node(node)

def redraw(self, *args) -> None:
self.canvas.clear()
self.canvas.after.clear()
self.draw_drag_selection()
for child in self.children:
if isinstance(child, MyWidget):
child.redraw()
self.canvas.add(child.canvas)

def draw_drag_selection(self) -> None:
self.dsw._redraw()
self.canvas.after.add(self.dsw.canvas)

def reset_drag_selection(self) -> None:
self.drag_selecting = False
self.dsw.size = 0., 0.

def on_touch_down(self, touch):
if super(MyLayout, self).on_touch_down(touch):
return True

if getattr(touch, 'button', None) == 'right':
touch.grab(self)
self.reset_drag_selection()
self.drag_selecting = True
self.dsw.pos = touch.pos
return True
return super(MyLayout, self).on_touch_down(touch)

def on_touch_move(self, touch):
if touch.grab_current is self and self.drag_selecting:
self.dsw.width += touch.dx
self.dsw.height += touch.dy
self._redraw()
for child in self.children:
if isinstance(child, MyWidget) and self.dsw.collide_widget(child):
self.select_node(child)
else:
self.deselect_node(child)
return True
return super(MyLayout, self).on_touch_move(touch)

def on_touch_up(self, touch):
if touch.grab_current is self and self.drag_selecting:
touch.ungrab(self)
self.reset_drag_selection()
return super(MyLayout, self).on_touch_up(touch)


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


if __name__ == '__main__':
MyApp().run()

Elliot Garbus

unread,
Nov 13, 2020, 10:20:16 AM11/13/20
to kivy-...@googlegroups.com

I have not had a chance to closely look at your code but I have 2 thoughts:

  1. I have always viewed CompoundSelectBehavior to be used for actions like ctrl-click, and shift-click to select multi widgets in a list, like selecting multiple files in a dialog box.  It does not look like that is what you want here. 
  2. You don’t want to test for widget_collision, looking at the animation you attached – you want to select the child widgets that are surrounded.

 

I don’t fully understand your intent, but if you simply want to select the child widgets that are ‘captured’ by the controller, I think it would be more straight forward to just code that up.

 

I think you would need to overload add_widget in MyLayout to add the appropriate behaviors to the child widgets…. But it might be better to just take a different approach.

 

I’ll take a closer look in  a few hours….

 

From: GJG
Sent: Thursday, November 12, 2020 8:04 PM
To: kivy-...@googlegroups.com
Subject: [kivy-users] collide_widget() not working as expected

 

I'm trying to implement drag selection like below:

I try to do this by creating a layout that inherits from CompoundSelectionBehavior and FloatLayout. This custom layout has a widget representing the blue selection box in the gif above. I tried to use collide_widget() to select other widgets, but it doesn't work as expected. Here's the code and a gif:

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CA%2Bqqht6GcmfYbDCiqOtfxrkaoo50J%3DDPFS4yfwH%2BPf49JdoUKA%40mail.gmail.com.

 

drag-select.gif

GJG

unread,
Nov 13, 2020, 10:57:21 AM11/13/20
to kivy-...@googlegroups.com
Hi Elliot. It may be hard to see from the first gif I sent, but I want any widget that touches the blue selection box to be selected. Think of selecting multiple Desktop icons on a Windows or UNIX system. Here's a better gif:


I want to single select widgets with a click and multiselect with ctrl+click, shift+click, and drag-select as shown in the gifs I linked. From my understanding, I should use collide_widget and CompoundSelectBehavior.

You received this message because you are subscribed to a topic in the Google Groups "Kivy users support" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/kivy-users/YjSwDepGGqg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/5faea424.1c69fb81.61f6d.7c12SMTPIN_ADDED_MISSING%40gmr-mx.google.com.
drag-and-select.gif

morsatici

unread,
Nov 13, 2020, 7:40:11 PM11/13/20
to Kivy users support
I've discovered the problem: collide_widget() assumes that the width and height of a widget are positive.

The way I modified the DragSelection widget's width and height would allow the either of the dimensions to be negative. I had to modify collide_widget() to make this work properly. Here's the working code:

from kivy.app import App
from kivy.clock import Clock
from kivy.config import Config
from kivy.graphics import Color, Line, Rectangle
from kivy.lang import Builder
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.uix.behaviors.compoundselection import CompoundSelectionBehavior
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget

Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
kv = '''
<MyWidget>:
    size_hint: 0.2, 0.2

MyLayout:
    multiselect: True  # this has to be True or you can only select one widget at a time
    def collide_widget(self, wid: Widget) -> bool:
        """overwrite collide widget to account for negative widths and heights"""
        if max(self.right, self.x) < wid.x:
            return False
        if min(self.x, self.right) > wid.right:
            return False
        if max(self.top, self.y) < wid.y:
            return False
        if min(self.y, self.top) > wid.top:
            return False
        return True
            self.dsw.width += touch.dx  #  this can make width negative
            self.dsw.height += touch.dy  # this can make height negative
            self._redraw()
            for child in self.children:
                if isinstance(child, MyWidget) and self.dsw.collide_widget(child):
                    self.select_node(child)
                else:
                    self.deselect_node(child)
            return True
        return super(MyLayout, self).on_touch_move(touch)

    def on_touch_up(self, touch):
        if touch.grab_current is self and self.drag_selecting:
            touch.ungrab(self)
            self.reset_drag_selection()
        return super(MyLayout, self).on_touch_up(touch)


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


if __name__ == '__main__':
    MyApp().run()

Elliot Garbus

unread,
Nov 14, 2020, 12:30:00 PM11/14/20
to kivy-...@googlegroups.com

Well Done.  Glad to hear you sorted it out!

from kivy.app import App

--

Reply all
Reply to author
Forward
0 new messages