Rendering text to a kivy canvas - python

I am trying to draw my own graphic within a kivy 'canvas'. For now I have a red or green rectangle which changes colour once per second, but I want to add a changing text label.
After a little searching it appears that there isn't a "Text" Instruction which can be added to the canvas. I have found a few references to using a Label() widget as well as the canvas Instructions, but this does not seem ideal, and also I can't seem to get it to render more than once.
Here's my object as it stands at the moment:
class HVObject(BoxLayout):
def __init__(self, **kwargs):
BoxLayout.__init__(self, **kwargs)
self.colour = 1
self.label = Label()
self.render()
self.add_widget(self.label)
self.bind(size=self._update_rect, pos=self._update_rect)
Clock.schedule_interval(self.callevery, 1)
def render(self):
self.canvas.clear()
self.rect = Rectangle(size=self.size, pos=self.pos)
self.canvas.add(Color(1-self.colour, self.colour, 0, 1))
self.canvas.add(self.rect)
self.label.text = "COL %d" % self.colour
self.canvas.ask_update()
def callevery(self, x):
self.colour = 1-self.colour
self.render()
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
self.label.pos = instance.pos
Is there an easy way to achieve the effect I need?
Thank you

Answering my own question:
After a little look around the [kivy] garden, I found Tickline (and Tick). and the use of CoreLabel() and Rectangle(texture=...)
Here's my updated render() method which adds the text object I need.
def render(self):
self.canvas.clear()
self.canvas.add(Color(1-self.colour, self.colour, 0, 1))
self.rect = Rectangle(size=self.size, pos=self.pos)
self.canvas.add(self.rect)
label = CoreLabel(text="COL %d" % self.colour, font_size=20)
label.refresh()
text = label.texture
self.canvas.add(Color(self.colour, 1-self.colour,0, 1))
pos = list(self.pos[i] + (self.size[i] - text.size[i]) / 2 for i in range(2))
self.canvas.add(Rectangle(size=text.size, pos=pos, texture=text))
self.canvas.ask_update()
Which works for me, albeit a little clunky!

Related

Tkitnter canvas widget events

I'm making a tkinter gui and I want it to work like this:
I have a frame, inside the frame there's a canvas and inside the canvas there are multiple rectangles
I want to make it that once I hover over a rectangle it's color will change from white, to green
simple, right?
so help me figure out what's wrong
Here's the class:
class guiSong:
def __init__(self, master: tkinter.Canvas, songobject: SongFile, x, y, rect=None):
self.master = master
self.songobject = songobject
self.x = x
self.y = y
self.rect = rect
def on_enter(self, event):
self.master.itemconfig(self.rect, fill='green')
print("Should change to green rect ", str(self.rect))
def on_leave(self, enter):
self.master.itemconfig(self.rect, fill='white')
def display(self):
self.rect = self.master.create_rectangle(self.x, self.y, self.x + 1150, self.y + 150, fill='white', tags = ['playbutton',self.songobject])
print("Self Rect is "+str(self.rect)+"!!!!!!!!!!!!!!!!!!!!!!!")
self.master.tag_bind('playbutton',"<Enter>", self.on_enter)
self.master.tag_bind('playbutton',"<Leave>", self.on_leave)
self.albumimg = Image.open(BytesIO(self.songobject.albumimage))
self.albumimg = ImageOps.expand(self.albumimg,border=5)
self.albumimg = self.albumimg.resize((120, 120), Image.ANTIALIAS)
self.img = ImageTk.PhotoImage(self.albumimg)
make_image(self.img, self.x + 25, self.y + 15, self.master)
print(f"Creating image {str(self.img)} at x",self.x+25, " y ",self.y+15 )
return self.img
#self.master.create_image(self.x + 25, self.y + 15, anchor = tkinter.W,image=img)
Don't bother the whole songobject stuff that's unrelated
I made a list of those objects and displayed them all inside a canvas one after another
The expected output is that once I hover over a rectangle it'll turn green
what happens in reality is that only the last rectangle created is colored once hovering over any rectangle.
Maybe this can help you, it's a rectangle that becomes green when you hover it with mouse, red when you leave it, and blue if you click on it.
Note : for blue color, I made an example with an argument in the callback function.
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.can = tk.Canvas(self, width=200, height=200)
self.can.pack()
self.rect = self.can.create_rectangle(50, 50, 100, 100, fill="gray")
self.can.tag_bind(self.rect, '<Enter>', self.green_rect)
self.can.tag_bind(self.rect, '<Leave>', self.red_rect)
self.can.tag_bind(self.rect, '<Button-1>', lambda x:self.color_rect("blue"))
def color_rect(self, color):
self.can.itemconfigure(self.rect, fill=color)
def green_rect(self, event=None):
self.can.itemconfigure(self.rect, fill="green")
def red_rect(self, event=None):
self.can.itemconfigure(self.rect, fill="red")
gui = GUI()
gui.mainloop()
Since each rectangle is an instance of guiSong, you can directly bind to the canvas item rather than to a tag.
Here's a simplified version of your class:
class guiSong:
def __init__(self, master, songobject, x, y):
self.master = master
tags = ("playbutton", songobject)
self.rect = master.create_rectangle(x,y,x+100, y+100, tags=tags, fill="white")
self.master.tag_bind(self.rect, "<Enter>", self.on_enter)
self.master.tag_bind(self.rect, "<Leave>", self.on_leave)
def on_enter(self, event):
self.master.itemconfigure(self.rect, fill="red")
def on_leave(self, event):
self.master.itemconfigure(self.rect, fill="white")
If you wish to bind to the tag, you can use the tag "current" to refer to the object that received the event.
class guiSong:
...
def on_enter(self, event):
self.master.itemconfigure("current", fill="red")
def on_leave(self, event):
self.master.itemconfigure("current", fill="white")

How to make multiple fbo work in kivy?

I'm working on 3d with kivy.I wanted to make bloom effect by postprocessing shader.I dont know how to pass multiple fbo in kivy..i can make one pass but cant make multiple pass..so,can any one help me?
Here is my code:
class SergioGameEngine(Widget):
def __init__(self, **kwargs):
self.canvas = RenderContext(size = self.size,clear_color=(0.5,0.5,0.5,1.0))
with self.canvas:
self.viewport = Rectangle(size=(self.size), pos=self.pos)
self.fbo = Fbo(size=self.size,
with_depthbuffer=True,
compute_normal_mat=True,
clear_color=(0.5, 0.5, 0.5, 1.))
self.fbo.shader.source = resource_find('simple.glsl')
Loading meshes.....
with self.fbo:
self.cb = Callback(self.setup_gl_context)
PushMatrix()
self.setup_scene()
PopMatrix()
self.cb = Callback(self.reset_gl_context)
self.canvas.shader.fs = fsi #this is postprocessing shader
self.pm = Window.render_context['projection_mat']
self.mm = Window.render_context['modelview_mat']
self.canvas['projection_mat'] = self.pm
self.canvas['modelview_mat'] = self.mm
def on_pos(self, instance, value):
self.viewport.pos = (0.0,0.0)
print(value)
def on_texture(self, instance, value):
self.viewport.texture = self.fbo.texture
fsi = """postprocessing shader"""
Now,canvas viewport is updated with fbo texture...
and the viewport texture is used as sampler on canvas fragment shader and i can edit that screen texture ...but the thing is how can i take canvas outputed frametexture and pass again as sampler for reusing as screen shader?
:)

Nesting widgets in kivy

I'm trying to make an interface in kivy and I think there are some fundamental things I don't understand about custom widgets and how to hierarchy them, even after going through the tutorial. I think I have more of a box-model html mindset, so the way widgets are nested in native GUIs are still kind of foreign to me.
Some background:
I consulted this entry on how to add a background (It answers the question: "How to add a background image/color/video/... to a Layout", which I believe I was replicating with the _update_rect methods).
This one that has an on_touch_down event.
K, I'm trying to get MyApp to look like this...
As I understand it, here are the things I'd need for that:
You have an app.
The app has a root.
The root has a background, say the background is white.
The background contains a holder, say the holder has a little margin from the background and is gray. I do want this to be a widget rather than just a non-widget canvas because I want the holder to react to click events, as well. It turns random colors when clicked. (Just to show it's doing something.)
The holder contains two custom widgets. These are circles with labels, each colored green. They turn random colors when clicked (just to show they're doing something).
Here's my code, which doesn't crash anymore, but doesn't display any colors or objects of any kind.
#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Color, Ellipse, Rectangle
class MyApp(App):
title = 'My App'
def build(self):
root = RootWidget()
root.bind(
size=self._update_rect,
pos=self._update_rect)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 0, 0, 1) # This RED does not display.
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="some junk!") # This label does not display.
mybackground = Background()
self.add_widget(mybackground)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Background(Widget):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 1, 1, 1) # This WHITE does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This label does not display.
myholder = Holder()
self.add_widget(myholder)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Holder(Widget):
def __init__(self, **kwargs):
super(Holder, self).__init__(**kwargs)
with self.canvas.before:
Color(0.25, 0.25, 0.25, 1) # This GRAY does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This does not display.
c1 = Circley(label="Label 1") # I see I'd need to do some size/pos math here to center
c2 = Circley(label="Label 2") # but since everything isn't working, I've tabled this.
self.add_widget(c1)
self.add_widget(c2)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
class Circley(Widget):
def __init__(self, label='label', **kwargs):
super(Circley, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1) # This GREEN does not display
self.circ = Ellipse(
size=self.size,
pos=self.pos,
text=label
)
def _update_circ(self, instance, value):
self.circ.pos = instance.pos
self.circ.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
if __name__ == '__main__':
MyApp().run()
Any pointers on what I'm doing wrong and how to nest these widgets correctly?
The reason you get a blank screen is that your app's build() method does not return anything. Whatever it returns would be the root widget, but even though you make some widgets you don't return one so nothing is displayed.
If you fix this, you can run the app again but you'll immediately get an error something like MyApp has no attribute rect. This is because your root widget is immediately sized and positioned to fill the window, which (as per your root.bind line) triggers MyApp._update_rect. However, this method try to modify MyApp.rect.pos...but the app doesn't have a self.rect! You presumably intended to bind to root._update_rect, not the app's method. (Edit: I bound to root._update_rect instead and now at least the red background and green circle do appear.)
Edit: And as a side note, this would be a lot easier using the kv language, which could automatically take care of a lot of the widget creation, rectangle binding etc.
I don't have time to fix it all right now, but perhaps these two problems can help you fix the overall flow. I'll try to post a more complete answer later if nobody else has.
Here's an updated MyApp, as per the comments.
class MyApp(App):
title = 'My App'
def build(self):
root = RootWidget()
root.bind(
size=root._update_rect,
pos=root._update_rect)
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.graphics import Color, Rectangle, Ellipse
class MyApp(App):
title = 'My App'
def build(self):
root = RootWidget()
root.bind(
size=root._update_rect,
pos=root._update_rect)
return root
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 0, 0, 1) # This RED does not display.
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="some junk!") # This label does not display.
mybackground = Background()
self.add_widget(mybackground)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Background(Widget):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
with self.canvas.before:
Color(1, 1, 1, 1) # This WHITE does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This label does not display.
myholder = Holder()
self.add_widget(myholder)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
class Holder(Widget):
def __init__(self, **kwargs):
super(Holder, self).__init__(**kwargs)
with self.canvas.before:
Color(0.25, 0.25, 0.25, 1) # This GRAY does not display
self.rect = Rectangle(
size=self.size,
pos=self.pos,
text="More stuff!") # This does not display.
c1 = Circley(label="Label 1") # I see I'd need to do some size/pos math here to center
c2 = Circley(label="Label 2") # but since everything isn't working, I've tabled this.
self.add_widget(c1)
self.add_widget(c2)
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
class Circley(Widget):
def __init__(self, label='label', **kwargs):
super(Circley, self).__init__(**kwargs)
with self.canvas.before:
Color(0, 1, 0, 1) # This GREEN does not display
self.circ = Ellipse(
size=self.size,
pos=self.pos,
text=label
)
def _update_circ(self, instance, value):
self.circ.pos = instance.pos
self.circ.size = instance.size
def on_touch_down(self, touch):
rcolor = Color(random(), random(), random(), 1)
with self.canvas:
self.color = rcolor
if __name__ == '__main__':
MyApp().run()

Tkinter Canvas - Draw text on top

I have this class to create a Statusbar:
class Statusbar(Canvas):
'''Creates a statusbar widget'''
def __init__(self, master = None, **options):
if not master: master = Tk()
self.master, self.options = master, options
self.barFill, self.addText, self.value = self.options.get('barFill', 'red'), self.options.get('addText', True), 0
for option in ('barFill', 'addText'):
if option in self.options: del self.options[option]
Canvas.__init__(self, master, **self.options)
self.offset = self.winfo_reqwidth() / 100
self.height = self.winfo_reqwidth()
if self.addText: self.text = self.create_text(self.winfo_reqwidth()/2, self.winfo_reqheight()/2, text = '0%')
self.bar = self.create_rectangle(0, 0, self.value, self.height, fill = self.barFill)
def setValue(self, value):
'''Sets the value of the status bar as a percent'''
self.value = value * self.offset
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
def change(self, value):
'''Changes the value as a percent'''
self.value += (value * self.offset)
self.coords(self.bar, 0, 0, self.value, self.height)
if self.addText: self.itemconfigure(self.text, text = str(self.value/self.offset) + '%')
My issue is that the text is always drawn under rectangle. So when the rectangle reaches the text, you can't see the text anymore. How can I fix this? Thanks in advance.
The fact that one object sits atop another is called the stacking order. By default, objects created later have a higher stacking order than those that were created earlier. So, one solution is to draw the rectangle and then draw the text.
You can also move things up or down the stacking order using the lift and lower commands of a canvas. You must give it an id or tag of what you want to lift or lower, and optionally an id or tag of the object you want the first object(s) to be above or below.
So, for example, you could raise the text above the rectangle like this:
self.lift(self.text, self.bar)
If you want to get really fancy, you can create the notion of layers. I gave an example in another answer, here: https://stackoverflow.com/a/9576938/7432
In my programming class, we said put whatever text you don't want to be blocked drawn last. So put the text at the bottom of what function you are using to draw with

Need help working with self.MemoryDC in wxPython

I'm trying to make a custom text widget that is double buffered (In order to avoid flicker).
However, I'd like to be able to do a few things. Yet, I'm unsure of the exact methods I should use.
The first two are easy I simply want to change the background and foreground color.
So more or less I want to be able to change the text color for self.Text in self.Draw().
Snippet:
self.Text = mdc.DrawText(self.TextString, 10, 0)
As sell as the Background (fill) color for self.MemoryDC.
Next, does anyone know how I could center self.Text? Finally, how do I configure self.Text after it has been created?
The widget thus far:
class DynamicText (wx.Panel):
def __init__(self, par):
self.Par = par
wx.Panel.__init__(self, self.Par)
self.Time = Time(self, func=self.SetTime)
self.Dim = self.Par.GetClientSize()
self.SetSize(self.Dim)
self.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.Erase)
self.Bind(wx.EVT_PAINT, self.Paint)
def Set (self, text) :
self.TextString = text
def SetTime (self, time) :
self.Set(str(time))
self.Resize(None)
def Resize(self, event):
self.Width, self.Height = self.GetSize()
bitmap = wx.EmptyBitmap(self.Width, self.Height)
self.MemoryDC = wx.MemoryDC(bitmap)
''' Redraws **self.MemoryDC** '''
mdc = self.MemoryDC
''' Deletes everything from widget. '''
mdc.Clear()
fs = 11
font = wx.Font( fs, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
mdc.SetFont(font)
self.Draw()
self.Refresh()
def Draw (self) :
mdc = self.MemoryDC
self.Text = mdc.DrawText(self.TextString, 10, 0)
def Erase(self, event):
''' Does nothing, as to avoid flicker. '''
pass
def Paint(self, event):
pdc = wx.PaintDC(self)
w, h = self.MemoryDC.GetSize()
pdc.Blit(0, 0, w, h, self.MemoryDC, 0, 0)
I don't understand what you mean by configuring self.Text after it was created. If you want to change the text after you've drawn it - you can't. Once you've drawn it to the DC it's there, and the only way to change it would be to clear the DC and repaint it. In your case, it seems all you need to do when the text is updated is to call Resize() again, forcing a redraw. Note that DrawText() retruns nothing, so the value of your self.Text would be None. You definitely can't use that to refer to the drawn text. :D
As for the rest, here's an example of a Draw() method that centers the text and paints it blue:
def Draw(self) :
mdc = self.MemoryDC
dc_width, dc_height = mdc.GetSizeTuple()
text_width, text_height, descent, externalLeading = mdc.GetFullTextExtent(self.TextString)
x = (dc_width - text_width) / 2
y = (dc_height - text_height) / 2
mdc.SetTextForeground('Blue')
mdc.DrawText(self.TextString, x, y)

Categories