Change canvas drawing order in kivy - python

I have an image widget in my project and I keep on adding Line objects to its canvas according to touch input (it's a simple drawing app only with an image background). However, at some point, I change something in the screen (long story short it's actually a scrollview containing a boxlayout which contains the image, and I add more images to it during run time to make an infinite image), and the lines that were on the screen disappear. I checked and noticed that they are still inside the canvas's children list, but just not being displayed on the screen. I am however still able to draw more lines. What can cause such behavior? I even tried redrawing the old Line() objects from when they were still displayed on the screen and still nothing happens...
Here's the relevant code:
python
class NotebookScreen(GridLayout):
def __init__(self, **kwargs):
global main_screen
self.rows = 1
super(NotebookScreen, self).__init__(**kwargs)
self.bind(pos=self.update_notebook, size=self.update_notebook, on_touch_up=self.release_touch_func)
def arrow_up_on_press(self):
global scroll_up_event
if scroll_up_event is not None:
scroll_up_event.cancel()
scroll_up_event = None
scroll_up_event = Clock.schedule_interval(self.scroll_up, 0.1)
def arrow_down_on_press(self):
global scroll_down_event
if scroll_down_event is not None:
scroll_down_event.cancel()
scroll_down_event = None
scroll_down_event = Clock.schedule_interval(self.scroll_down, 0.1)
def arrow_down_on_release(self):
global scroll_down_event
if scroll_down_event is not None:
scroll_down_event.cancel()
scroll_down_event = None
def arrow_up_on_release(self):
global scroll_up_event
if scroll_down_event is not None:
scroll_up_event.cancel()
scroll_up_event = None
def scroll_down(self, arg):
global scrolls
scrl = main_screen.ids.notebook_scroll
if scrl.scroll_y - get_scroll_distance()[0] > 0:
scrl.scroll_y -= get_scroll_distance()[0]
scrolls += get_scroll_distance()[1]
else:
offset = get_scroll_distance()[0] - scrl.scroll_y
scrl.scroll_y = 0
main_screen.ids.notebook_scroll.on_scroll_y(0, 0, offset=offset)
def scroll_up(self, arg):
global scrolls
scrl = main_screen.ids.notebook_scroll
if scrl.scroll_y + get_scroll_distance()[0] < 1.:
scrl.scroll_y += get_scroll_distance()[0]
scrolls -= get_scroll_distance()[1]
else:
scrl.scroll_y = 1
def update_notebook(self, a, b, **kwargs):
for child in self.ids.notebook_image.children:
child.size = MyImage.get_size_for_notebook(child)
def release_touch_func(self, a1, a2, **kwargs):
global scroll_up_event, scroll_down_event
if scroll_up_event is not None:
scroll_up_event.cancel()
scroll_up_event = None
if scroll_down_event is not None:
scroll_down_event.cancel()
scroll_down_event = None
class MyScrollView(ScrollView):
def __init__(self, **kwargs):
super(MyScrollView, self).__init__(**kwargs)
def on_scroll_y(self, instance, scroll_val, offset=0):
global main_screen, gen_id, scrolls
if self.scroll_y == 0.: # < get_scroll_distance()[0]:
box = main_screen.ids.notebook_image
old_height = box.height
old_pos_y = self.scroll_y
new_image = MyImage()
new_image.id = next(gen_id)
box.add_widget(new_image)
old_height = (len(main_screen.ids.notebook_image.children) - 1) * main_screen.ids.notebook_image.children[
0].height
self.scroll_y = new_image.height / (old_height + new_image.height) - offset * box.height / old_height
print([child.id for child in list(main_screen.ids.notebook_image.children)])
# redraw all text from earlier
for image in main_screen.ids.notebook_image.children:
image.draw_all_lines()
def slider_change(self, s, instance, value):
if value >= 0:
# this to avoid 'maximum recursion depth exceeded' error
s.value = value
def scroll_change(self, scrlv, instance, value):
scrlv.scroll_y = value
class MyImage(Image):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.lines = []
self.line_coords = []
self.line_objects = []
def get_size_for_notebook(self, **kwargs):
global img_size
width, height = Window.size
return width, (max(img_size[0] * height / width, height))
def to_image(self, x, y):
''''
Convert touch coordinates to pixels
:Parameters:
`x,y`: touch coordinates in parent coordinate system - as provided by on_touch_down()
:Returns: `x, y`
A value of None is returned for coordinates that are outside the Image source
'''
# get coordinates of texture in the Canvas
pos_in_canvas = self.center_x - self.norm_image_size[0] / 2., self.center_y - self.norm_image_size[1] / 2.
# calculate coordinates of the touch in relation to the texture
x1 = x - pos_in_canvas[0]
y1 = y - pos_in_canvas[1]
# convert to pixels by scaling texture_size/source_image_size
if x1 < 0 or x1 > self.norm_image_size[0]:
x2 = None
else:
x2 = self.texture_size[0] * x1 / self.norm_image_size[0]
if y1 < 0 or y1 > self.norm_image_size[1]:
y2 = None
else:
y2 = self.texture_size[1] * y1 / self.norm_image_size[1]
return x2, y2
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
current_touch = self.to_image(*touch.pos)
self.add_to_canvas_on_touch_down((touch.x, touch.y))
touch.ud['line'] = Line(points=[touch.x, touch.y])
with self.canvas:
Color(0, 0, 1, 1)
l = Line(points=touch.ud['line'].points)
self.line_objects.append(l)
return True
else:
return super(MyImage, self).on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
current_touch = self.to_image(*touch.pos)
self.add_to_canvas_on_touch_move((touch.x, touch.y))
touch.ud['line'].points += (touch.x, touch.y)
with self.canvas:
Color(0, 0, 1, 1)
l = Line(points=touch.ud['line'].points)
self.line_objects.append(l)
return True
else:
return super(MyImage, self).on_touch_move(touch)
def add_to_canvas_on_touch_down(self, point):
with self.canvas:
self.line_coords.append([point])
self.lines.append([point[0], point[1]])
def add_to_canvas_on_touch_move(self, point):
with self.canvas:
self.lines[-1].append(point[0])
self.lines[-1].append(point[1])
self.line_coords[-1].append(point)
def draw_all_lines(self):
with self.canvas.after:
Color(0, 0, 1, 1)
Line(points=line)
kv:
MyScrollView:
bar_color: [1, 0, 0, 1]
id: notebook_scroll
padding: 0
spacing: 0
do_scroll: (False, False) # up and down
BoxLayout:
padding: 0
spacing: 0
orientation: 'vertical'
id: notebook_image
size_hint: 1, None
height: self.minimum_height
MyImage:
MyImage:
<MyImage>:
source: 'images/pic.png'
allow_stretch: True
keep_ratio: False
size: root.get_size_for_notebook()
size_hint: None, None

One solution is to draw the lines on the parent of the Images (the BoxLayout). And since the Images are added to the bottom of the BoxLayout, the y coordinates of the lines must be adjusted as new Images are added below them.
Here is a modified version of your MyImage class that does this:
class MyImage(Image):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.line_coords = {} # a dictionary with line object as key and coords as value
def get_size_for_notebook(self, **kwargs):
global img_size
width, height = Window.size
return width, (max(img_size[0] * height / width, height))
def to_image(self, x, y):
''''
Convert touch coordinates to pixels
:Parameters:
`x,y`: touch coordinates in parent coordinate system - as provided by on_touch_down()
:Returns: `x, y`
A value of None is returned for coordinates that are outside the Image source
'''
# get coordinates of texture in the Canvas
pos_in_canvas = self.center_x - self.norm_image_size[0] / 2., self.center_y - self.norm_image_size[1] / 2.
# calculate coordinates of the touch in relation to the texture
x1 = x - pos_in_canvas[0]
y1 = y - pos_in_canvas[1]
# convert to pixels by scaling texture_size/source_image_size
if x1 < 0 or x1 > self.norm_image_size[0]:
x2 = None
else:
x2 = self.texture_size[0] * x1 / self.norm_image_size[0]
if y1 < 0 or y1 > self.norm_image_size[1]:
y2 = None
else:
y2 = self.texture_size[1] * y1 / self.norm_image_size[1]
return x2, y2
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
with self.parent.canvas.after:
Color(0, 0, 1, 1)
touch.ud['line'] = Line(points=[touch.x, touch.y])
# add dictionary entry for this line
# save y coord as distance from top of BoxLayout
self.line_coords[touch.ud['line']] = [touch.x, self.parent.height - touch.y]
return True
else:
return super(MyImage, self).on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
touch.ud['line'].points += (touch.x, touch.y)
# save touch point with y coordinate as the distance from the top of the BoxLayout
self.line_coords[touch.ud['line']].extend([touch.x, self.parent.height - touch.y])
return True
else:
return super(MyImage, self).on_touch_move(touch)
def draw_all_lines(self):
with self.parent.canvas.after:
Color(0, 0, 1, 1)
for line, pts in self.line_coords.items():
# create new list of points
new_pts = []
for i in range(0, len(pts), 2):
new_pts.append(pts[i])
# calculate correct y coord (height of BoxLayout has changed)
new_pts.append(self.parent.height - pts[i+1])
# redraw this line using new_pts
Line(points=new_pts)
To use this capability, modify part of your MyScrollView:
def on_scroll_y(self, instance, scroll_val, offset=0):
global main_screen, gen_id, scrolls
if self.scroll_y == 0.: # < get_scroll_distance()[0]:
box = main_screen.ids.notebook_image
old_height = box.height
old_pos_y = self.scroll_y
new_image = MyImage()
new_image.id = next(gen_id)
box.add_widget(new_image)
old_height = (len(main_screen.ids.notebook_image.children) - 1) * main_screen.ids.notebook_image.children[
0].height
self.scroll_y = new_image.height / (old_height + new_image.height) - offset * box.height / old_height
print([child.id for child in list(main_screen.ids.notebook_image.children)])
# use Clock.schedule_once to do the drawing after heights are recalculated
Clock.schedule_once(self.redraw_lines)
def redraw_lines(self, dt):
# redraw all text from earlier
self.ids.notebook_image.canvas.after.clear()
for image in self.ids.notebook_image.children:
image.draw_all_lines()

Related

Get location of pixel upon click in Kivy

I am using Kivy to design an application to draw an n-sided polygon over a live video stream to demarcate regions of interest. The problem I have is that Kivy provides coordinates w.r.t to the whole window and not just the image. What I would like is to have the location of the pixel (in x and y cords) clicked. I have looked at to_local() method but it didn't make much sense, neither did it produce desired results. Any help would be appreciated, below is the MRE.
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Color, Ellipse, Line
from random import random
class ImageView(Image):
def on_touch_down(self, touch):
##
# This provides touch cords for the entire app and not just the location of the pixel clicked#
print("Touch Cords", touch.x, touch.y)
##
color = (random(), 1, 1)
with self.canvas:
Color(*color, mode='hsv')
d = 30.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class DMSApp(App):
def build(self):
imagewidget = ImageView(source="/home/red/Downloads/600.png")
imagewidget.size_hint = (1, .5)
imagewidget.pos_hint = {"top": 1}
layout = BoxLayout(size_hint=(1, 1))
layout.add_widget(imagewidget)
return layout
if __name__ == '__main__':
DMSApp().run()
You can calculate the coordinates of the touch relative to the lower left corner of the actual image that appears in the GUI. Those coordinates can then be scaled to the actual size of the source image to get a reasonable estimate of the actual pixel coordinates within the source. Here is a modified version of your in_touch_down() method that does that (only minimal testing performed):
def on_touch_down(self, touch):
if not self.collide_point(*touch.pos):
return super(ImageView, self).on_touch_down(touch)
lr_space = (self.width - self.norm_image_size[0]) / 2 # empty space in Image widget left and right of actual image
tb_space = (self.height - self.norm_image_size[1]) / 2 # empty space in Image widget above and below actual image
print('lr_space =', lr_space, ', tb_space =', tb_space)
print("Touch Cords", touch.x, touch.y)
print('Size of image within ImageView widget:', self.norm_image_size)
print('ImageView widget:, pos:', self.pos, ', size:', self.size)
print('image extents in x:', self.x + lr_space, self.right - lr_space)
print('image extents in y:', self.y + tb_space, self.top - tb_space)
pixel_x = touch.x - lr_space - self.x # x coordinate of touch measured from lower left of actual image
pixel_y = touch.y - tb_space - self.y # y coordinate of touch measured from lower left of actual image
if pixel_x < 0 or pixel_y < 0:
print('clicked outside of image\n')
return True
elif pixel_x > self.norm_image_size[0] or \
pixel_y > self.norm_image_size[1]:
print('clicked outside of image\n')
return True
else:
print('clicked inside image, coords:', pixel_x, pixel_y)
# scale coordinates to actual pixels of the Image source
print('actual pixel coords:',
pixel_x * self.texture_size[0] / self.norm_image_size[0],
pixel_y * self.texture_size[1] / self.norm_image_size[1], '\n')
color = (random(), 1, 1)
with self.canvas:
Color(*color, mode='hsv')
d = 30.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))
touch.ud['line'] = Line(points=(touch.x, touch.y))
return True

Kivy custom shaders touch events

Am using custom shader on my game, and i must because of performance. Now i am at the point where i want to bind touch events to my particles/Ufo so that i can decide what to do when someone touches them, but i don't know how i can calculate their width and height. I am currently able to tell where a touch event happened but my collide_point function always return False because i don't have the correct Width and Height of my game particle's. collide_point function require particle's right and top and particle's right and top requires particle's width and height to work. In the documentation it is said that
the width and the height property is subject to layout logic
but am not using any Layout, am using Widget instead. How can i calculate my game particle's width and height. Below is the code
from __future__ import division
from collections import namedtuple
import json
import math
import random
from kivy import platform
from kivy.app import App
from kivy.base import EventLoop
from kivy.clock import Clock
from kivy.core.image import Image
from kivy.core.window import Window
from kivy.graphics import Mesh
from kivy.graphics.instructions import RenderContext
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
import base64
UVMapping = namedtuple('UVMapping', 'u0 v0 u1 v1 su sv')
GLSL = """
---vertex
$HEADER$
attribute vec2 vCenter;
attribute float vScale;
void main(void)
{
tex_coord0 = vTexCoords0;
mat4 move_mat = mat4
(1.0, 0.0, 0.0, vCenter.x,
0.0, 1.0, 0.0, vCenter.y,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
vec4 pos = vec4(vPosition.xy * vScale, 0.0, 1.0)
* move_mat;
gl_Position = projection_mat * modelview_mat * pos;
}
---fragment
$HEADER$
void main(void)
{
gl_FragColor = texture2D(texture0, tex_coord0);
}
"""
with open("game.glsl", "wb") as glslc:
glslc.write(GLSL)
def load_atlas():
atlas = json.loads('''{"game-0.png": {"Elien": [2, 26, 100, 100]}}''')
tex_name, mapping = atlas.popitem()
data = '''iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAACJklEQVR4nO3dy1ICQRAF0YLw/39ZVxMBGCjEMF23JvOsXPgounMaN61VQrtsH3x3TqFfLv9/ykdcq9z8RKv25Lro5yiUAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAfXUPsNM79ydWXbYZZVoAey7MPH6tQdScAI64KbV9T3QI6QGsuCKHDiH5l8DVd1aRd2QTT4DOjcCdBmknQMpTmDLH4ZICSFv0tHkOkRJA6mKnzvUxCQGkL3L6fLskBKBG3QFMebqmzPm2zgCmLeq0eV/SfQKoWVcAU5+mqXM/5QkA1xHA9Kdo+vx3PAHgDADOAOBWB3CW98+zvA5PADoDgDMAOAOAMwA4A4AzADgDgDMAuNUBnOXCxVlehycAnQHAGQBcRwDT3z+nz3/HEwCuK4CpT9HUuZ/yBIDrDGDa0zRt3pd0nwBTFnXKnG/rDkDNEgJIf7rS59slIYCq3EVOnetjUgKoylvstHkOkRRAVc6ip8xxuMS/E7gtfsflC8zGb9JOgFurNwO3+VWZJ8CtFacBcuM36QFsjggBvfGbKQFsHjfNfxix07QAHrmpOyX/EqgFDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwA7lrl7YpE7okkSZIkSZIkSZIkSZIkSZIkSZL+9AMvSSThyPfOhQAAAABJRU5ErkJggg=='''
with open(tex_name, "wb") as co:
co.write(base64.b64decode(data))
tex = Image(tex_name).texture
tex_width, tex_height = tex.size
uvmap = {}
for name, val in mapping.items():
x0, y0, w, h = val
x1, y1 = x0 + w, y0 + h
uvmap[name] = UVMapping(
x0 / tex_width, 1 - y1 / tex_height,
x1 / tex_width, 1 - y0 / tex_height,
0.5 * w, 0.5 * h)
return tex, uvmap
class Particle:
x = 0
y = 0
size = 1
def __init__(self, parent, i):
self.parent = parent
self.vsize = parent.vsize
self.base_i = 4 * i * self.vsize
self.reset(created=True)
def update(self):
for i in range(self.base_i,
self.base_i + 4 * self.vsize,
self.vsize):
self.parent.vertices[i:i + 3] = (
self.x, self.y, self.size)
def reset(self, created=False):
raise NotImplementedError()
def advance(self, nap):
raise NotImplementedError()
class GameScreen(Widget):
indices = []
vertices = []
particles = []
def __init__(self, **kwargs):
Widget.__init__(self, **kwargs)
self.canvas = RenderContext(use_parent_projection=True)
self.canvas.shader.source = "game.glsl"
self.vfmt = (
(b'vCenter', 2, 'float'),
(b'vScale', 1, 'float'),
(b'vPosition', 2, 'float'),
(b'vTexCoords0', 2, 'float'),
)
self.vsize = sum(attr[1] for attr in self.vfmt)
self.texture, self.uvmap = load_atlas()
def on_touch_down(self, touch):
for w in self.particles:
if w.collide_point(*touch.pos):
w.reset() #Not Working
return super(GameScreen, self).on_touch_down(touch)
def on_touch_move(self, touch):
for w in self.particles:
if w.collide_point(*touch.pos):
w.reset() #Not Working
return super(GameScreen, self).on_touch_move(touch)
def make_particles(self, Ap, num):
count = len(self.particles)
uv = self.uvmap[Ap.tex_name]
for i in range(count, count + num):
j = 4 * i
self.indices.extend((
j, j + 1, j + 2, j + 2, j + 3, j))
self.vertices.extend((
0, 0, 1, -uv.su, -uv.sv, uv.u0, uv.v1,
0, 0, 1, uv.su, -uv.sv, uv.u1, uv.v1,
0, 0, 1, uv.su, uv.sv, uv.u1, uv.v0,
0, 0, 1, -uv.su, uv.sv, uv.u0, uv.v0,
))
p = Ap(self, i)
self.particles.append(p)
def update_glsl(self, nap):
for p in self.particles:
p.advance(nap)
p.update()
self.canvas.clear()
with self.canvas:
Mesh(fmt=self.vfmt, mode='triangles',
indices=self.indices, vertices=self.vertices,
texture=self.texture)
class Ufo(Particle):
plane = 2.0
tex_name = 'Elien'
texture_size = 129
right = top = 129
def reset(self, created=False):
self.plane = random.uniform(2.0, 2.8)
self.x = random.randint(15, self.parent.right-15)
self.y = self.parent.top+random.randint(100, 2500)
self.size = random.uniform(0.5, 1.0) #every particle must have a random size
self.top = self.size * self.texture_size
self.right = self.size * self.texture_size
def collide_point(self, x, y):
'''Check if a point (x, y) is inside the Ufo's axis aligned bounding box.'''
with open('TouchFeedback.txt', 'wb') as c:
c.write(str(x)+', '+str(y))
return self.x <= x <= self.right and self.y <= y <= self.top
def advance(self, nap):
self.y -= 100 * self.plane * nap
if self.y < 0:
self.reset()
class Game(GameScreen):
def initialize(self):
self.make_particles(Ufo, 20)
def update_glsl(self, nap):
GameScreen.update_glsl(self, nap)
class GameApp(App):
def build(self):
EventLoop.ensure_window()
return Game()
def on_start(self):
self.root.initialize()
Clock.schedule_interval(self.root.update_glsl, 60 ** -1)
if __name__ == '__main__':
Window.clearcolor = get_color_from_hex('111110')
GameApp().run()
I think you are not calculating/updating your top and right properties of your Ufo objects. Also, since your GL code considers (x,y) to be the center of your circle, but Kivy considers (x,y) to be the lower left corner of an object, you need to keep that in mind. In order to correctly calculate collide_point(), I have added left and bottom properties to your Ufo, and use those properties to calculate collisions. Here is an updated version of your code with those changes:
from __future__ import division
import kivy
from kivy.config import Config
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
Config.set('modules', 'monitor', '')
from collections import namedtuple
import json
import math
import random
from kivy import platform
from kivy.app import App
from kivy.base import EventLoop
from kivy.clock import Clock
from kivy.core.image import Image
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.graphics import Mesh
from kivy.graphics.instructions import RenderContext
from kivy.properties import NumericProperty
from kivy.uix.widget import Widget
from kivy.utils import get_color_from_hex
import base64
UVMapping = namedtuple('UVMapping', 'u0 v0 u1 v1 su sv')
GLSL = """
---vertex
$HEADER$
attribute vec2 vCenter;
attribute float vScale;
void main(void)
{
tex_coord0 = vTexCoords0;
mat4 move_mat = mat4
(1.0, 0.0, 0.0, vCenter.x,
0.0, 1.0, 0.0, vCenter.y,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
vec4 pos = vec4(vPosition.xy * vScale, 0.0, 1.0)
* move_mat;
gl_Position = projection_mat * modelview_mat * pos;
}
---fragment
$HEADER$
void main(void)
{
gl_FragColor = texture2D(texture0, tex_coord0);
}
"""
with open("game.glsl", "wb") as glslc:
glslc.write(GLSL.encode())
def load_atlas():
atlas = json.loads('''{"game-0.png": {"Elien": [2, 26, 100, 100]}}''')
tex_name, mapping = atlas.popitem()
data = '''iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAACJklEQVR4nO3dy1ICQRAF0YLw/39ZVxMBGCjEMF23JvOsXPgounMaN61VQrtsH3x3TqFfLv9/ykdcq9z8RKv25Lro5yiUAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAGQCcAcAZAJwBwBkAnAHAfXUPsNM79ydWXbYZZVoAey7MPH6tQdScAI64KbV9T3QI6QGsuCKHDiH5l8DVd1aRd2QTT4DOjcCdBmknQMpTmDLH4ZICSFv0tHkOkRJA6mKnzvUxCQGkL3L6fLskBKBG3QFMebqmzPm2zgCmLeq0eV/SfQKoWVcAU5+mqXM/5QkA1xHA9Kdo+vx3PAHgDADOAOBWB3CW98+zvA5PADoDgDMAOAOAMwA4A4AzADgDgDMAuNUBnOXCxVlehycAnQHAGQBcRwDT3z+nz3/HEwCuK4CpT9HUuZ/yBIDrDGDa0zRt3pd0nwBTFnXKnG/rDkDNEgJIf7rS59slIYCq3EVOnetjUgKoylvstHkOkRRAVc6ip8xxuMS/E7gtfsflC8zGb9JOgFurNwO3+VWZJ8CtFacBcuM36QFsjggBvfGbKQFsHjfNfxix07QAHrmpOyX/EqgFDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwAzgDgDADOAOAMAM4A4AwA7lrl7YpE7okkSZIkSZIkSZIkSZIkSZIkSZL+9AMvSSThyPfOhQAAAABJRU5ErkJggg=='''
with open(tex_name, "wb") as co:
co.write(base64.b64decode(data))
tex = Image(tex_name).texture
tex_width, tex_height = tex.size
uvmap = {}
for name, val in mapping.items():
x0, y0, w, h = val
x1, y1 = x0 + w, y0 + h
uvmap[name] = UVMapping(
x0 / tex_width, 1 - y1 / tex_height,
x1 / tex_width, 1 - y0 / tex_height,
0.5 * w, 0.5 * h)
return tex, uvmap
class Particle(EventDispatcher):
# x = 0
# y = 0
x = NumericProperty(0)
y = NumericProperty(0)
size = 1
def __init__(self, parent, i):
super(Particle, self).__init__()
self.parent = parent
self.vsize = parent.vsize
self.base_i = 4 * i * self.vsize
self.reset(created=True)
def update(self):
for i in range(self.base_i,
self.base_i + 4 * self.vsize,
self.vsize):
self.parent.vertices[i:i + 3] = (
self.x, self.y, self.size)
def reset(self, created=False):
raise NotImplementedError()
def advance(self, nap):
raise NotImplementedError()
class GameScreen(Widget):
indices = []
vertices = []
particles = []
def __init__(self, **kwargs):
Widget.__init__(self, **kwargs)
self.canvas = RenderContext(use_parent_projection=True)
self.canvas.shader.source = "game.glsl"
self.vfmt = (
(b'vCenter', 2, 'float'),
(b'vScale', 1, 'float'),
(b'vPosition', 2, 'float'),
(b'vTexCoords0', 2, 'float'),
)
self.vsize = sum(attr[1] for attr in self.vfmt)
self.texture, self.uvmap = load_atlas()
def on_touch_down(self, touch):
for w in self.particles:
if w.collide_point(*touch.pos):
w.reset() #Not Working
return super(GameScreen, self).on_touch_down(touch)
def on_touch_move(self, touch):
for w in self.particles:
if w.collide_point(*touch.pos):
w.reset() #Not Working
return super(GameScreen, self).on_touch_move(touch)
def make_particles(self, Ap, num):
count = len(self.particles)
uv = self.uvmap[Ap.tex_name]
for i in range(count, count + num):
j = 4 * i
self.indices.extend((
j, j + 1, j + 2, j + 2, j + 3, j))
self.vertices.extend((
0, 0, 1, -uv.su, -uv.sv, uv.u0, uv.v1,
0, 0, 1, uv.su, -uv.sv, uv.u1, uv.v1,
0, 0, 1, uv.su, uv.sv, uv.u1, uv.v0,
0, 0, 1, -uv.su, uv.sv, uv.u0, uv.v0,
))
p = Ap(self, i)
self.particles.append(p)
def update_glsl(self, nap):
for p in self.particles:
p.advance(nap)
p.update()
self.canvas.clear()
self.canvas.before.clear() # temporary
with self.canvas.before: # temporary code block
for p in self.particles:
Rectangle(pos=(p.left, p.bottom), size=(p.size*p.texture_size, p.size*p.texture_size))
with self.canvas:
Mesh(fmt=self.vfmt, mode='triangles',
indices=self.indices, vertices=self.vertices,
texture=self.texture)
class Ufo(Particle):
plane = 2.0
tex_name = 'Elien'
texture_size = 129
right = NumericProperty(129)
top = NumericProperty(129)
left = NumericProperty(0)
bottom = NumericProperty(0)
def reset(self, created=False):
self.plane = random.uniform(2.0, 2.8)
self.size = random.uniform(0.5, 1.0) #every particle must have a random size
self.x = random.randint(15, self.parent.right-15)
self.y = self.parent.top+random.randint(100, 2500)
def collide_point(self, x, y):
'''Check if a point (x, y) is inside the Ufo's axis aligned bounding box.'''
return self.left <= x <= self.right and self.bottom <= y <= self.top
def advance(self, nap):
self.y -= 100 * self.plane * nap
if self.y < 0:
self.reset()
def on_x(self, instance, new_x):
self.right = new_x + self.size * self.texture_size / 2.0
self.left = new_x - self.size * self.texture_size / 2.0
def on_y(self, instance, new_y):
self.top = new_y + self.size * self.texture_size / 2.0
self.bottom = new_y - self.size * self.texture_size / 2.0
class Game(GameScreen):
def initialize(self):
self.make_particles(Ufo, 20)
def update_glsl(self, nap):
GameScreen.update_glsl(self, nap)
class GameApp(App):
def build(self):
EventLoop.ensure_window()
return Game()
def on_start(self):
self.root.initialize()
Clock.schedule_interval(self.root.update_glsl, 60 ** -1)
if __name__ == '__main__':
Window.clearcolor = get_color_from_hex('111110')
GameApp().run()
I have also adding drawing the Ufo bounding box using canvas.before. This is just to visualize the clickable area for each Ufo, and can be easily removed.
Try using collide_widget()
Snippets
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
for w in self.particles:
if self.collide_widget(w):
w.reset()
return True
return super(GameScreen, self).on_touch_down(touch)
Widget class ยป collide_widget
collide_widget(wid)
Check if another widget collides with this widget. This function performs an axis-aligned bounding box intersection test by default.
Parameters:
wid: Widget class
Widget to test collision with.
Returns:
bool. True if the other widget collides with this widget, False otherwise.

How to find the area and perimter of a rectangle that has two points defined?

I have a Point class and Rectangle class set up, here is the code for that:
import math
class Point:
"""A point in two-dimensional space."""
def __init__(self, x: float = 0.0, y: float = 0.0)->None:
self.x = x
self.y = y
def moveIt(self, dx: float, dy: float)-> None:
self.x = self.x + dx
self.y = self.y + dy
def distance(self, otherPoint: float):
if isinstance(otherPoint, Point):
x1 = self.x
y1 = self.y
x2 = otherPoint.x
y2 = otherPoint.y
return ( (x1 - x2)**2 + (y1 - y2)**2 )**0.5
class Rectangle:
def __init__(self, topLeft = Point(0,0), bottomRight = Point(1,1)):
self.topLeft = topLeft
self.bottomRight = bottomRight
The two points are the top left and the bottom right for the rectangle. How could I find the area and perimeter of this rectangle from two points? Would appreciate any and all help!
We can access the x and y values of each point and calculate the height and width, from there we can create methods that calculate area and perimeter
class Rectangle():
def __init__(self, topLeft = Point(0,0), bottomRight = Point(1,1)):
self.topLeft = topLeft
self.bottomRight = bottomRight
self.height = topLeft.y - bottomRight.y
self.width = bottomRight.x - topLeft.x
self.perimeter = (self.height + self.width) * 2
self.area = self.height * self.width
rect = Rectangle(Point(3,10),Point(4,8))
print(rect.height)
print(rect.width)
print(rect.perimeter)
print(rect.area)
chrx#chrx:~/python/stackoverflow/9.24$ python3.7 rect.py
2
1
6
2
Or using methods
class Rectangle():
def __init__(self, topLeft = Point(0,0), bottomRight = Point(1,1)):
self.topLeft = topLeft
self.bottomRight = bottomRight
self.height = topLeft.y - bottomRight.y
self.width = bottomRight.x - topLeft.x
def make_perimeter(self):
self.perimeter = (self.height + self.width) * 2
return self.perimeter
def make_area(self):
self.area = self.height * self.width
return self.area
rect = Rectangle(Point(3,10),Point(4,8))
print(rect.height)
print(rect.width)
print(rect.make_perimeter())
print(rect.make_area())

Python Kivy. How do I change orientation of Pagelayout?

I need to change orientation to vertical, but it does not work the same way as BoxLayout. There is also no information about this in the Kivy official documentation. In addition, is there any way to change the page by swiping from any place on the screen and not only the border?
Python:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
class MainScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("final.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
Kivy:
ScreenManagement:
MainScreen:
<MainScreen>:
canvas:
Rectangle:
source: "nakedman.jpg"
pos: self.pos
size: self.size
name: "main"
PageLayout:
orientation: "vertical"
BoxLayout:
Button:
text: "Button1"
Button:
text: "Button2"
BoxLayout:
Button:
text: "Button3"
Button:
text: "Button4"
PageLayout does not have the orientation attribute, so you have to build everything, in this case use the source code of PageLayout to obtain this new code:
__all__ = ('PageLayoutVertical', )
from kivy.uix.layout import Layout
from kivy.properties import NumericProperty, DictProperty
from kivy.animation import Animation
class PageLayoutVertical(Layout):
'''PageLayout class. See module documentation for more information.
'''
page = NumericProperty(0)
'''The currently displayed page.
:data:`page` is a :class:`~kivy.properties.NumericProperty` and defaults
to 0.
'''
border = NumericProperty('50dp')
'''The width of the border around the current page used to display
the previous/next page swipe areas when needed.
:data:`border` is a :class:`~kivy.properties.NumericProperty` and
defaults to 50dp.
'''
swipe_threshold = NumericProperty(.5)
'''The thresold used to trigger swipes as percentage of the widget
size.
:data:`swipe_threshold` is a :class:`~kivy.properties.NumericProperty`
and defaults to .5.
'''
anim_kwargs = DictProperty({'d': .5, 't': 'in_quad'})
'''The animation kwargs used to construct the animation
:data:`anim_kwargs` is a :class:`~kivy.properties.DictProperty`
and defaults to {'d': .5, 't': 'in_quad'}.
.. versionadded:: 1.11.0
'''
def __init__(self, **kwargs):
super(PageLayoutVertical, self).__init__(**kwargs)
trigger = self._trigger_layout
fbind = self.fbind
fbind('border', trigger)
fbind('page', trigger)
fbind('parent', trigger)
fbind('children', trigger)
fbind('size', trigger)
fbind('pos', trigger)
def do_layout(self, *largs):
l_children = len(self.children) - 1
w = self.width
x_parent, y_parent = self.pos
p = self.page
border = self.border
half_border = border / 2.
top = self.top
height = self.height - border
for i, c in enumerate(reversed(self.children)):
if i < p:
y = y_parent
elif i == p:
if not p: # it's first page
y = y_parent
elif p != l_children: # not first, but there are post pages
y = y_parent + half_border
else: # not first and there are no post pages
y = y_parent + border
elif i == p + 1:
if not p: # second page - no left margin
y = top - border
else: # there's already a left margin
y = top - half_border
else:
y = top
c.width = w
c.height = height
Animation(
x=x_parent,
y=y,
**self.anim_kwargs).start(c)
def on_touch_down(self, touch):
if (
self.disabled or
not self.collide_point(*touch.pos) or
not self.children
):
return
page = self.children[-self.page - 1]
if self.y <= touch.y < page.y:
touch.ud['page'] = 'previous'
touch.grab(self)
return True
elif page.top <= touch.y < self.top:
touch.ud['page'] = 'next'
touch.grab(self)
return True
return page.on_touch_down(touch)
def on_touch_move(self, touch):
if touch.grab_current != self:
return
p = self.page
border = self.border
half_border = border / 2.
page = self.children[-p - 1]
if touch.ud['page'] == 'previous':
# move next page upto right edge
if p < len(self.children) - 1:
self.children[-p - 2].y = min(
self.top - self.border * (1 - (touch.sy - touch.osy)),
self.top)
# move current page until edge hits the right border
if p >= 1:
b_right = half_border if p > 1 else border
b_left = half_border if p < len(self.children) - 1 else border
self.children[-p - 1].y = max(min(
self.y + b_left + (touch.y - touch.oy),
self.top - b_right),
self.y + b_left)
# move previous page left edge upto left border
if p > 1:
self.children[-p].y = min(
self.y + half_border * (touch.sy - touch.osy),
self.y + half_border)
elif touch.ud['page'] == 'next':
# move current page upto left edge
if p >= 1:
self.children[-p - 1].y = max(
self.y + half_border * (1 - (touch.osy - touch.sy)),
self.y)
# move next page until its edge hit the left border
if p < len(self.children) - 1:
b_right = half_border if p >= 1 else border
b_left = half_border if p < len(self.children) - 2 else border
self.children[-p - 2].y = min(max(
self.top - b_right + (touch.y - touch.oy),
self.y + b_left),
self.top - b_right)
# move second next page upto right border
if p < len(self.children) - 2:
self.children[-p - 3].y = max(
self.top + half_border * (touch.sy - touch.osy),
self.top - half_border)
return page.on_touch_move(touch)
def on_touch_up(self, touch):
if touch.grab_current == self:
if (
touch.ud['page'] == 'previous' and
abs(touch.y - touch.oy) / self.height > self.swipe_threshold
):
self.page -= 1
elif (
touch.ud['page'] == 'next' and
abs(touch.y - touch.oy) / self.height > self.swipe_threshold
):
self.page += 1
else:
self._trigger_layout()
touch.ungrab(self)
if len(self.children) > 1:
return self.children[-self.page + 1].on_touch_up(touch)
if __name__ == '__main__':
from kivy.base import runTouchApp
from kivy.uix.button import Button
pl = PageLayoutVertical()
for i in range(1, 10):
b = Button(text='page%s' % i)
pl.add_widget(b)
runTouchApp(pl)

Nested Widget's canvas do not draw anything

I've got two three nested widget which handle the on_touch_down event, clicking on the first parent, the event is sensed to the second and then the third. With this click the third widget should draw on it's canvas but this it's not happening.
I understand the dynamic of the canvas object and it's groups. So I don't really understand why this is happening
class Spline_guide(DragBehavior, Button):
def __init__(self, **kw):
super(Spline_guide, self).__init__(**kw)
self.bind(pos = self.update_spline , size = self.update_spline)
def update_spline(self, *l):
self.pos = self.parent.pos
return self
def show_spline(self,*l):
print 'show spline'
with self.canvas:
Color(0,1,0)
######## THIS ISTRUCTION DOESN'T WORK
Ellipse(pos = (100,100), size = (100,100))
return self
def hide_spline(self, *l):
print 'hide spline'
self.canvas.clear()
return self
class Editable_point(DragBehavior, Widget):
def __init__(self, name,x,y, **kw):
super(Editable_point, self).__init__(**kw)
self.drag_rectangle = (0,0 ,800,300)
self.drag_timeout = 10000000
self.drag_distance = 0
self.name = name
self.pos = (x - DOT_DIMENSION / 2,y - DOT_DIMENSION / 2)
self.size = (DOT_DIMENSION, DOT_DIMENSION)
self.spline = Spline_guide()
self.add_widget(self.spline)
self.SHOW_SPLINE = False
self.bind(pos = self.check_pos)
def check_pos(self, *l):
self.area = self.parent.parent
self.x = self.x if self.x > self.area.x else self.area.x
self.y = self.y if self.y > self.area.y else self.area.y
return self
def draw_point(self):
self.area = self.parent.parent
self.drag_rectangle = (self.area.x, self.area.y, self.area.width, self.area.height)
self.canvas.clear()
with self.canvas:
Color(1,0,0)
Ellipse(pos=self.pos, size=self.size)
return self
def on_enter(self, pop):
def wrap(l):
if l.text in ['start','stop']:
print 'you can not use start or stop as name'
pop.dismiss()
return wrap
if self.name in ['start','stop']:
pop.dismiss()
print 'you can not edit the name of start or stop point'
return wrap
self.name = l.text
pop.dismiss()
return wrap
def show_info(self, *l):
graph = self.parent.parent.graph
X_OFFSET = graph._plot_area.x + self.parent.x - DOT_DIMENSION / 2
Y_OFFSET = graph._plot_area.y + self.parent.y - DOT_DIMENSION / 2
_x = self.x - X_OFFSET
_y = self.y - Y_OFFSET
x, y = normalize(graph.xmin,graph.ymin,graph.xmax,graph.ymax,graph._plot_area.width,graph._plot_area.height, _x, _y)
point_name = TextInput(text=self.name, multiline = False)
point_info = Popup(title ="X: {} Y: {}".format(x,y), content = point_name, size_hint=(None,None), size=(200,100))
point_name.bind(on_text_validate=self.on_enter(point_info))
point_info.open()
return self
def on_touch_down(self, *l):
if self.SHOW_SPLINE:
self.SHOW_SPLINE = False
self.spline.hide_spline()
else:
self.SHOW_SPLINE = True
self.spline.show_spline()
return self
class Editable_line(Widget):
def __init__(self, **kw):
super(Editable_line, self).__init__(**kw)
self._old_ = [100,100]
self.old_pos = [0,0]
self.bind(pos=self.update_line, size=self.update_line)
def replace_start_stop(self, new_elem):
elem = filter(lambda x: x.name == new_elem.name, [ i for i in self.children if hasattr(i,'name')])
if elem: self.remove_widget(elem[0])
self.add_widget(new_elem)
return self
def update_points(self):
r_x = float(Window.size[0]) / float(self._old_[0])
r_y = float(Window.size[1]) / float(self._old_[1])
for p in [ i for i in self.children if hasattr(i,'name')]:
new_x = p.x * r_x
new_y = p.y * r_y
p.x = new_x
p.y = new_y
p.size = (DOT_DIMENSION, DOT_DIMENSION)
return self
def update_line(self, *a):
self.pos = self.parent.pos
self.size = self.parent.size
self.parent.graph.pos = self.pos
self.parent.graph.size = self.size
self.parent.graph._redraw_size()
#Coordinate per start e stop personalizzare sul graph
y = self.parent.graph._plot_area.y + self.y
x = self.parent.graph._plot_area.x + self.x
h = self.parent.graph._plot_area.height
w = self.parent.graph._plot_area.width
self.replace_start_stop(Editable_point('start', x, y + h / 2))
self.replace_start_stop(Editable_point('stop', x + w, y + h / 2))
self.update_points()
self._old_ = Window.size
return self.draw_line()
def sort_points(self):
self.children = sorted(self.children , key=attrgetter('x'))
return self
def control_presence(self,coordinates):
x = int(coordinates.x)
y = int(coordinates.y)
x_range = range(x-DOT_DIMENSION, x+DOT_DIMENSION)
y_range = range(y-DOT_DIMENSION, y+DOT_DIMENSION)
for p in [ i for i in self.children if hasattr(i,'name')]:
if int(p.x) in x_range and int(p.y) in y_range: return p
return False
def on_touch_down(self,coordinates):
#add points
p = self.control_presence(coordinates)
if p:
if not coordinates.is_double_tap:
return p.on_touch_down(coordinates)
return p.show_info(coordinates)
x = int(coordinates.x)
y = int(coordinates.y)
p = Editable_point('new point', x, y)
p.size = (DOT_DIMENSION, DOT_DIMENSION)
self.add_widget(p)
return self.draw_line()
def remove_point(self,coordinates):
p = self.control_presence(coordinates)
if p:
if p.name in ['start','stop']: print 'you can\'t delete start or stop point' else: self.remove_widget(p)
return self.draw_line()
def draw_line(self):
self.sort_points()
self.canvas.before.clear()
_l = list()
for p in [ i for i in self.children if hasattr(i,'name')]:
_l.append(p.x + DOT_DIMENSION/2)
_l.append(p.y + DOT_DIMENSION/2)
p.draw_point()
with self.canvas.before:
Color(0,0.8,1)
Line(points=_l, witdth = LINE_WIDTH)
return self
def on_touch_move(self, coordinates):
p = self.control_presence(coordinates)
if p:
if p.name in ['start','stop']:
return self.parent.on_touch_up(coordinates)
p.on_touch_move(coordinates)
self.draw_line()
return self
This is a piece of my code and show how the classes are linked. The Spline_guide doesn't draw anything. Ideas?
Widgets which are added to another Widget are drawn in the latter Widget's canvas. When you call self.canvas.clear() (i.e. in Editable_point) you are removing all the child canvases.
You could use canvas.before or canvas.after for your drawing instead, or you can save the instructions and modify them later:
def __init__(self, **kwargs):
...
with self.canvas:
self.draw_color = Color(0, 1, 0, 1)
self.draw_ellipse = Ellipse(pos=self.pos, size=self.size)
def draw_point(self):
...
self.draw_ellipse.pos = self.pos
self.draw_ellipse.size = self.size
Then you never need to clear the canvas at all. This is the preferred solution as it offers the best performance.

Categories

Resources