Changing drawing order of widgets?

1,024 views
Skip to first unread message

Martin Eriksson

unread,
Feb 12, 2014, 7:31:10 PM2/12/14
to kivy-...@googlegroups.com
Hello!

I've been trying to find the answer to this problem all day, but I couldn't find anything about it in the docs, nor Google.

In the code below I wrote a layout to contain draggable lines. My problem is that when you drag the lines upwards, the non-selected lines are drawn in front of the selected one. How would I change the order of which they are drawn?

import kivy
from kivy.app import App
from kivy.uix.layout import Layout
from kivy.uix.widget import Widget
from random import random
from kivy.graphics import Color, Rectangle

kivy
.require('1.8.0')

class DragLine(Widget):

   
def __init__(self, **kwargs):
       
super(DragLine, self).__init__(**kwargs)
       
self.grabbed = False
       
self.randomized = (random(), random(), random())
       
with self.canvas.after:
           
Color(*self.randomized)
           
self.rect = Rectangle(pos=self.pos, size=self.size)
       
self.bind(pos=self.redraw)
       
self.bind(size=self.redraw)

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

   
def on_touch_down(self, touch):
       
if self.collide_point(*touch.pos):
           
self.grabbed = True
           
self.y = touch.y - 15

   
def on_touch_move(self, touch):
       
if self.grabbed is not False:
           
self.y = touch.y - 15
           
self.parent.do_layout()

   
def on_touch_up(self, touch):
       
if self.grabbed is not None:
           
self.grabbed = False
           
self.parent.do_layout()

class DragLayout(Layout):

   
def __init__(self, **kwargs):
       
super(DragLayout, self).__init__(**kwargs)
       
self.bind(
            children
=self._trigger_layout,
            parent
=self._trigger_layout,
            size_hint
=self._trigger_layout,
            pos
=self._trigger_layout,
            size
=self._trigger_layout)

   
def do_layout(self, *largs):
        order
= sorted(self.children, key=lambda x: self.height + x.y, reverse=True)
       
for i, c in enumerate(order):
           
if c.grabbed is False:
                c
.x = self.x
                c
.y = self.height - order.index(c) * c.height
                c
.width = self.width

class Drag(App):

   
def build(self):
        cl
= DragLayout()
       
for i in range(10):
            cl
.add_widget(DragLine(height=30))
       
return cl

if __name__ == "__main__":
   
Drag().run()

Thomas Hansen

unread,
Feb 12, 2014, 10:05:00 PM2/12/14
to kivy-...@googlegroups.com
When you start dragging a widget, you could remove it or swap it with a blank placeholder widget and temporarily insert the one being dragged into another layer/widget on top of your layout. or at the end of the layout (but in the later case, your layout probably needs to know about how the dragging works / be pretty tightly covered). 

Then when you drop it/ on touch up insert it back into the right spot or whatever else you need to have happen. 
--
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.
For more options, visit https://groups.google.com/groups/opt_out.

Martin Eriksson

unread,
Feb 13, 2014, 4:15:56 AM2/13/14
to kivy-...@googlegroups.com
Thanks for a quick answer!

Before I started writing a temporary widget I decided to try reordering the draw calls one more time. Turns out all I needed was a few hours of sleep to find a solution.

I figured out that by reordering the layout's canvas.children I can change the order of which they are drawn. Basically, the following method will change a Layout's (any parent?) draw order to have the child drawn on top.

    def move_canvas(self, child):
       
self.canvas.remove(child.canvas)
       
self.canvas.insert(len(self.canvas.get_group(None)), child.canvas)


ZenCODE

unread,
Feb 13, 2014, 4:48:55 AM2/13/14
to kivy-...@googlegroups.com
And you also have a "canvas.before" and a "canvas.after" drawing group for even more flexibility. Kivy rocks! ;-)

Martin Eriksson

unread,
Feb 13, 2014, 5:34:00 AM2/13/14
to kivy-...@googlegroups.com
Looked at my code again, and noticed I actually used canvas.after. Just a remainder from when I tried it yesterday :)

After another half hour of looking at this, I couldn't get ZenCODE's solution to work, even if I use canvas.after for the child, the other non-selected children will be drawn over the selected one. I suspect this means that before and after only operate on the child's canvas. This, in turn, means the ordering is:
  • Child 1 canvas.before
  • Child 1 canvas
  • Child 1 canvas.after
  • Child 2 canvas.before
  • Child 2 canvas
  • And so on...
Basically, to achieve what I wanted I would have to do the drawing from the Layout rather than the child.

So thanks for your answer ZenCODE, even if it wasn't exactly what I needed I learned a lot from it.
Reply all
Reply to author
Forward
0 new messages