PyGTK Transparent Window - window

I want the window transparent, but the label to be 100% in opacity. How can I achieve this? BTW: I noticed when I upgraded to Ubuntu 12.04's unity interface that window.set_opacity wasn't working like it did on GNOME, but even if it did all the content inside the window would become transparent as well.
This is the code I started out with...
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import pango
import time
class Clock:
def __init__(self):
me = gtk.Window(gtk.WINDOW_TOPLEVEL)
me.connect("destroy", lambda w: gtk.main_quit())
me.set_decorated(False)
me.set_has_frame(False)
me.set_resizable(False)
me.set_property('skip-taskbar-hint', True)
self.label = gtk.Label()
self.label.modify_font(pango.FontDescription("FreeSerif Bold 50"))
attr = pango.AttrList()
fg_color = pango.AttrForeground(65535, 0, 0, 0, 65535)
attr.insert(fg_color)
self.label.set_attributes(attr)
me.add(self.label)
me.show_all()
def update(self):
self.label.set_text(time.strftime('%H:%M:%S'))
return True
clock = Clock()
gtk.timeout_add(200, clock.update)
gtk.main()
I found this topic on askubuntu and It's exactly what I was looking for however now I'm having problems having the digital clock show. Any help would be greatly appreciated.
Here's my code.
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import pango
import time
import cairo
class Clock (gtk.Window):
def __init__(self):
super(Clock, self).__init__()
self.connect("destroy", lambda w: gtk.main_quit())
self.set_decorated(False)
self.set_has_frame(False)
self.set_resizable(False)
self.set_property('skip-taskbar-hint', True)
self.label = gtk.Label()
self.label.modify_font(pango.FontDescription("FreeSerif Bold 50"))
attr = pango.AttrList()
fg_color = pango.AttrForeground(65535, 0, 0, 0, 65535)
attr.insert(fg_color)
self.label.set_attributes(attr)
self.screen = self.get_screen()
self.visual = self.screen.get_rgba_visual()
self.set_visual(self.visual)
self.set_app_paintable(True)
self.connect("draw", self.area_draw)
self.add(self.label)
self.show_all()
def update(self):
self.label.set_text(time.strftime('%H:%M:%S'))
return True
def area_draw(self, widget, cr):
cr.set_source_rgba(.2, .2, .2, 0.5)
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
clock = Clock()
gtk.timeout_add(200, clock.update)
gtk.main()

Well, you have to hack a lot to make a label like this.
The best is to reinvent the wheel: make a round one.
Draw your own label.
Get window context ( mywin.window.cairo_create())) and keep track of x, y.
def draw(*args):
ctx = win.window.cairo_create()
ctx.set_source_rgba(0, 0, 0, 0)
ctx.set_operator(0) # OPERATOR_CLEAR
ctx.paint()
ctx.set_source_rgba(0, .6, 1, .3)
ctx.arc(w/2, h/2, w/2, 0, 2*3.1415)
ctx.fill()
ctx.set_source_rgba(1, .8, 0, 1)
ctx.show_text('29-May-1234') #you have some work for font, style and so on
google gives you an hack that use ctypes for loading ttf font from file; it works very well on Linux( I have no idea on win).
NOTE: Above asume that you have an composit manager, or ... rgba for widget is None.
Also put an
colormap = self.win.get_screen().get_rgba_colormap()
if colormap == None: colormap = self.win.get_screen().get_rgb_colormap()
gtk.widget_set_default_colormap(colormap)
to set rgba if posible, and also it worth a check:
if self.win.is_composited():print ('OK!')
else:self.bg = self.capt_screen()
...........
def capt_screen(self):
x, y = self.win.get_position()
win = gtk.gdk.get_default_root_window()
w, h = win.get_size()
pb = gtk.gdk.Pixbuf(0 , False, 8, w, h)
self.win.hide()
pb = pb.get_from_drawable(win, win.get_colormap(), 0, 0, 0, 0, w, h)
self.win.show_all()
if (pb != None):
im = Image.fromstring('RGB', (w, h), pb.get_pixels())
im = im.crop((x, y, x + self._w, y + self._h))
im = im.convert('RGBA')
return im #or pb
The above is „xp” transparent efect: copy the bg and bledit with your win. Only if it is not a composit manager runing. Is ok for widget - stay on desk, but flickr for something else: at every refresh window has to hide itself, capture the gb, drawing and reveal.
PS: I have an working clock example, but I use png. If you like, i could send you an tar.gz on mail

Related

pygame does not access sprite functions after putting the dot "."

I am following a tutorial for a game of ships, and when creating the Player class in the init function I have a strange problem when wanting to access the functions of the Sprite module: when I write the name of the variable and put the point to access its functions remain in white, instead of being colored yellow indicating that it is a reserved word (I am using visual studio)
(Sorry if I do not express myself correctly I am using google translator.)
so the strange is this:
code screenshot
what is indicated in the red boxes should be in yellow (like what I underlined in yellow), since theoretically it would be accessing the functions of a Sprite object therefore it should be highlighted in yellow ...
the problem is that it appears white, as if it did not recognize the variable as a Sprite object
Here is where i create the image directory.
import pygame, random, os, sys
from os import path
img_dir = path.join(path.dirname(__file__), 'img')
And here is the Meteor class:
class Meteor(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image_orig = random.choice(meteor_images)
self.image_orig.set_colorkey("black")
self.image_orig.set_clip()
self.image = self.image_orig.copy()
self.rect = self.image.get_rect()
self.radius = int(self.rect.width*0.85/2) # *0.85 <- se leeria como (el 85% del width) / 2
pygame.draw.circle(self.image, "red", self.rect.center, self.radius)
self.rect.x = random.randrange(0, width)
self.rect.bottom = random.randrange(-100, -50)
self.speedx = random.randrange(-4, 4)
self.speedy = random.randrange(2, 8)
self.rot = 0
self.rot_speed = random.randrange(-8, 8)
self.last_update = pygame.time.get_ticks()
and then here is where create the meteors list:
meteor_images = []
meteor_list = ['meteorBrown_big1.png',
'meteorBrown_big2.png',
'meteorBrown_big3.png',
'meteorBrown_big4.png']
for img in meteor_list:
meteor_images.append(pygame.image.load(path.join(img_dir, img)).convert())
I am not very expert on the subject, what could be the problem??

Game Of Life - Embedding the pygame window in tkinter to add buttons [duplicate]

This question already has answers here:
Embedding a Pygame window into a Tkinter or WxPython frame
(3 answers)
Closed last year.
So I'm making a game in pygame, and I want to use tkinter as well. I embedded a pygame window into a tkinter window, but I can't seem to do anything with it.
For context, here is the full code:
import Tkinter as tk
import os
import platform
import pygame
class window(object):
def __init__(self):
self.root = tk.Tk() # Main window
self.root.title("SquareScape")
self.root.iconbitmap(r'C:\Users\17_es\PycharmProjects\square_puzzle\images\icon.ico')
self.root.configure(background='#9b9b9b')
# Large Frame
self.win_frame = tk.Frame(self.root, width=670, height=520, highlightbackground='#595959', highlightthickness=2)
# menu (left side)
self.menu = tk.Frame(self.win_frame, width=150, height=516, highlightbackground='#595959', highlightthickness=2)
self.menu_label = tk.Label(self.menu, text="Settings", bg='#8a8a8a', font=("Courier", "16", "bold roman"))
self.mute = tk.Button(self.menu, text="XXXX", font="Courier", bg='#bcbcbc', activebackground='#cdcdcd')
# pygame
self.pygame_frame = tk.Frame(self.win_frame, width=514, height=514, highlightbackground='#595959', highlightthickness=2)
self.embed = tk.Frame(self.pygame_frame, width=512, height=512,)
# Packing
self.win_frame.pack(expand=True)
self.win_frame.pack_propagate(0)
self.menu.pack(side="left")
self.menu.pack_propagate(0)
self.menu_label.pack(ipadx=60, ipady=2)
self.mute.pack(ipadx=40, ipady=2, pady=5)
self.pygame_frame.pack(side="left")
self.embed.pack()
#This embeds the pygame window
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
if platform.system == "Windows":
os.environ['SDL_VIDEODRIVER'] = 'windib'
#Start pygame
pygame.init()
self.win = pygame.display.set_mode((512, 512))
self.win.fill(pygame.Color(255, 255, 255))
pygame.display.init()
self.root.mainloop()
screen = window()
#Here is sample code that I want to run
pygame.draw.rect(screen.win, (0, 0, 255), (200, 200, 100, 100))
When I use pygame.draw.rect(screen.win, (0, 0, 255), (200, 200, 100, 100)), nothing happens. Using pygame inside the class worked, but in my more complicated game, using self.variable for all my variables seems unnecessary.
How can I run my code in the pygame window outside of the window class?
So - besides the obvious missing call to pygame.display.flip - which I suppose is what you intended with the call to pygame.display.init (pygame.init already calls that one) - what I found out is that tkinter needs to initialize its windows and widgets before the packed frame is fully available to be used by Pygame.
I did that by adding a call to self.root.update_idletasks() before calling pygame.init -- that, and explicitly setting the video driver for my platform (which you already does for Windows), made things work.
Anyway, also, in your code you did not show were you wanted to make the calls to Pygamedrawing functions - as it is, it is well possible that everything is correct, but the code after screen.window() is just never run (or rather, just run at program exit) - because you call tkinter.mainloop inside the __init__ method of your application class.
Moving the call to mainloop outside the __init__ is a good practice, so you can initialize other objects and resources as well - and you actualy do have the screen object to operate things on. By making that call inside __init__ is like your whole program was running "inside the initialization".
In short:
call tkinter.update_iddletasks() before initializing pygame
remember to call pygame.display.flip after you draw anything with Pygame
arrange your code so that your drawing calls are actually executed, and not blocked after the call to enter tkinter's loop
You should seriously consider using Python 3.7 or later - (the only "python 2" code there is import Tkinter which becomes import tkinter in Python 3). Python 2 is really at the end of line, and there are no updates for projects like pygame on it.
.
That said, here is your code, modified to run on Linux + Python 3 (should still work on Windows), and to actually perform some actions using the embedded pygame frame.
import tkinter as tk
import os
import platform
import pygame
import time
class window(object):
def __init__(self):
self.root = tk.Tk() # Main window
self.root.title("SquareScape")
# self.root.iconbitmap(r'C:\Users\17_es\PycharmProjects\square_puzzle\images\icon.ico')
self.root.configure(background='#9b9b9b')
# Large Frame
self.win_frame = tk.Frame(self.root, width=670, height=520, highlightbackground='#595959', highlightthickness=2)
# menu (left side)
self.menu = tk.Frame(self.win_frame, width=150, height=516, highlightbackground='#595959', highlightthickness=2)
self.menu_label = tk.Label(self.menu, text="Settings", bg='#8a8a8a', font=("Courier", "16", "bold roman"))
self.mute = tk.Button(self.menu, text="XXXX", font="Courier", bg='#bcbcbc', activebackground='#cdcdcd')
tk.Button(self.menu, text="<->", command=lambda: setattr(self, "direction", (-self.direction[0], self.direction[1]))).pack()
tk.Button(self.menu, text="^", command=lambda: setattr(self, "direction", (self.direction[0], -self.direction[1]))).pack()
# pygame
self.pygame_frame = tk.Frame(self.win_frame, width=514, height=514, highlightbackground='#595959', highlightthickness=2)
self.embed = tk.Frame(self.pygame_frame, width=512, height=512,)
# Packing
self.win_frame.pack(expand=True)
self.win_frame.pack_propagate(0)
self.menu.pack(side="left")
self.menu.pack_propagate(0)
self.menu_label.pack(ipadx=60, ipady=2)
self.mute.pack(ipadx=40, ipady=2, pady=5)
self.pygame_frame.pack(side="left")
self.embed.pack()
#This embeds the pygame window
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
system = platform.system()
if system == "Windows":
os.environ['SDL_VIDEODRIVER'] = 'windib'
elif system == "Linux":
os.environ['SDL_VIDEODRIVER'] = 'x11'
self.root.update_idletasks()
#Start pygame
pygame.init()
self.win = pygame.display.set_mode((512, 512))
self.bg_color = (255, 255, 255)
self.win.fill(self.bg_color)
self.pos = 0, 0
self.direction = 10, 10
self.size = 40
self.color = (0, 255, 0)
self.root.after(30, self.update)
self.root.mainloop()
def update(self):
first_move = True
pygame.draw.rect(self.win, self.bg_color, self.pos + (self.size, self.size))
self.pos = self.pos[0] + self.direction[0], self.pos[1] + self.direction[1]
if self.pos[0] < 0 or self.pos[0] > 512 - self.size:
self.direction = -self.direction[0], self.direction[1]
self.pos = self.pos[0] + 2 * self.direction[0], self.pos[1] + self.direction[1]
if self.pos[1] < 0 or self.pos[1] > 512 - self.size:
self.direction = self.direction[0], -self.direction[1]
self.pos = self.pos[0] + self.direction[0], self.pos[1] + 2 * self.direction[1]
pygame.draw.rect(self.win, self.color, self.pos + (self.size, self.size))
pygame.display.flip()
self.root.after(30, self.update)
screen = window()
tk.mainloop()

Maintaining a Cairo drawing in Gtk.DrawingArea() after scroll event

I'm working on an annotation tool for some images and decided to use GTK for the task. I have a Gtk.DrawingArea() nested inside Gtk.Viewport() which is nested in Gtk.ScrolledWindow() to enable scrolling of the drawing area. The drawing area contains an image and shapes are drawn on top of the image using Cairo on each mouse click event.
If I understand correctly, scrolling by default causes a redrawing of Gtk.DrawingArea() which makes all of shapes disappear. Is there any way (other than keeping a list of coordinates and redrawing every shape on each scroll event) to maintain those shapes?
import gi
import math
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Test")
self.drag = False
self.drag_x = 0
self.drag_y = 0
self.pos = []
viewport = Gtk.Viewport()
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.expose)
self.pixbuf = GdkPixbuf.Pixbuf.new_from_file("anntool/test.jpg")
self.darea.set_size_request(self.pixbuf.get_width(), self.pixbuf.get_height());
self.maximize() # maximize window on load
grid = Gtk.Grid()
self.add(grid)
scrolled = Gtk.ScrolledWindow()
scrolled.set_hexpand(True)
scrolled.set_vexpand(True)
scrolled.set_kinetic_scrolling(True)
self.v_scroll = scrolled.get_vadjustment()
self.h_scroll = scrolled.get_hadjustment()
scrolled.add_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK)
scrolled.connect("button-release-event", self.release)
scrolled.connect("button-press-event", self.click)
scrolled.connect("motion-notify-event", self.mousemove)
# scrolled.connect("scroll_event", self.scroll)
viewport.add(self.darea)
scrolled.add(viewport)
grid.add(scrolled)
def click(self, widget, event):
if (event.button == 1):
cr = self.darea.get_parent_window().cairo_create()
x = self.h_scroll.get_value() + event.x
y = self.v_scroll.get_value() + event.y
cr.arc(x, y, 10, 0, 2 * math.pi)
cr.set_source_rgba(0.0, 0.6, 0.0, 1)
cr.fill()
if (event.button == 2):
self.drag = True
self.drag_x = event.x
self.drag_y = event.y
self.pos = [self.h_scroll.get_value(), self.v_scroll.get_value()]
def release(self, widget, event):
self.drag = False
default = Gdk.Cursor(Gdk.CursorType.ARROW)
widget.get_window().set_cursor(default)
def mousemove(self, widget, event):
if self.drag:
self.h_scroll.set_value(self.pos[0] + self.drag_x - event.x)
self.v_scroll.set_value(self.pos[1] + self.drag_y - event.y)
hand = Gdk.Cursor(Gdk.CursorType.HAND1)
widget.get_window().set_cursor(hand)
def scroll(self, widget, event):
print("scrolled")
def expose(self, widget, event):
Gdk.cairo_set_source_pixbuf(event, self.pixbuf, 0, 0)
event.paint()
win = MainWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
As Uli Schlachter pointed out, redraw is executed on each scroll event, therefore one need to keep track of added points (e.g. with a list) and redraw each point in expose() function in the code above.

Matplotlib embedded in wxPython: TextCtrl in Navigation toolbar not working on macos

I'm doing a simple embedded graph with Matplotlib APIs (2.2.2) in wxPython (Phoenix 4.0.1) and Python 3.6.4. I have subclassed the WXAgg Navigation toolbar so I can remove the "configure subplots" tool and this is working fine.
In addition, I have added a read-only TextCtrl into my subclassed toolbar to show mouse coordinates (just like it appears in the pyplot state-based version of matplotlib). I've implemented a simple handler for the mouse move events per the Matplotlib docs and this is all working fine on Windows 10.
However, this code does not fully work on macOS (10.13.4 High Sierra). The graph displays just fine, the toolbar displays fine, the toolbar buttons work fine, but I don't get any display of my TextCtrl with the mouse coordinates in the toolbar (or even the initial value as set when I create the TextCtrl).
Can anyone shed light on why the TextCtrl in the Matplotlib toolbar doesn't work on the mac? Is there a way to do this on the mac? And if this is simply not possible, what are my alternatives for showing the mouse coordinates elsewhere in my Matplotlib canvas?
Here's my sample code:
import wx
from matplotlib.figure import Figure
from matplotlib import gridspec
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavigationToolbar
class MyToolbar(NavigationToolbar):
def __init__(self, plotCanvas):
# create the default toolbar
NavigationToolbar.__init__(self, plotCanvas)
# Add a control to display mouse coordinates
self.info = wx.TextCtrl(self, -1, value = 'Coordinates', size = (100,-1),
style = wx.TE_READONLY | wx.BORDER_NONE)
self.AddStretchableSpace()
self.AddControl(self.info)
# Remove configure subplots
SubplotsPosition = 6
self.DeleteToolByPos(SubplotsPosition)
self.Realize()
class Graph(wx.Frame):
def __init__(self, parent, title='Coordinates Test'):
super().__init__(parent, title=title)
self.SetSize((900, 500))
# A simple embedded matplotlib graph
self.fig = Figure(figsize = (8.2,4.2), facecolor = 'gainsboro')
self.canvas = FigureCanvas(self, -1, self.fig)
gs = gridspec.GridSpec(2, 1, left = .12, right = .9, bottom = 0.05, top = .9, height_ratios = [10, 1], hspace = 0.35)
ax = self.fig.add_subplot(gs[0])
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='About as simple as it gets, folks')
ax.grid()
ax.set_navigate(True)
# Get a toolbar instance
self.toolbar = MyToolbar(self.canvas)
self.toolbar.Realize()
# Connect to matplotlib for mouse movement events
self.canvas.mpl_connect('motion_notify_event', self.onMotion)
self.toolbar.update()
# Layout the frame
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.EXPAND)
self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
self.SetSizer(self.sizer)
def onMotion(self, event):
if event.inaxes:
xdata = event.xdata
ydata = event.ydata
self.toolbar.info.ChangeValue(f'x = {xdata:.1f}, y = {ydata:.1f}')
else:
self.toolbar.info.ChangeValue('')
class MyFrame(wx.Frame):
def __init__(self, parent, title=""):
super().__init__(parent, title=title)
self.SetSize((800, 480))
self.graph = Graph(self)
self.graph.Show()
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, title='Main Frame')
self.frame.Show()
return True
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
I realize this is late, but I think that the simplest solution is to not subclass NavigationToolbar at all, but just to add a TextCtrl of your own.
That is, getting rid of your MyToolbar altogether and modifying your code to be
# Get a toolbar instance
self.toolbar = NavigationToolbar(self.canvas)
self.info = wx.TextCtrl(self, -1, value = 'Coordinates', size = (100,-1),
style = wx.TE_READONLY | wx.BORDER_NONE)
self.canvas.mpl_connect('motion_notify_event', self.onMotion)
self.toolbar.update()
# Layout the frame
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.EXPAND)
bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
bottom_sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
bottom_sizer.Add(self.info, 1, wx.LEFT | wx.EXPAND)
self.sizer.Add(bottom_sizer, 0, wx.LEFT | wx.EXPAND)
self.SetSizer(self.sizer)
def onMotion(self, event):
if event.inaxes is not None:
xdata = event.xdata
ydata = event.ydata
self.info.ChangeValue(f'x = {xdata:.1f}, y = {ydata:.1f}')
else:
self.info.ChangeValue('')
will give TextCtrl that does display the motion events.

Pygame: importing fonts causes game to stall [duplicate]

Is there a way I can display text on a pygame window using python?
I need to display a bunch of live information that updates and would rather not make an image for each character I need.
Can I blit text to the screen?
Yes. It is possible to draw text in pygame:
# initialize font; must be called after 'pygame.init()' to avoid 'Font not Initialized' error
myfont = pygame.font.SysFont("monospace", 15)
# render text
label = myfont.render("Some text!", 1, (255,255,0))
screen.blit(label, (100, 100))
You can use your own custom fonts by setting the font path using pygame.font.Font
pygame.font.Font(filename, size): return Font
example:
pygame.font.init()
font_path = "./fonts/newfont.ttf"
font_size = 32
fontObj = pygame.font.Font(font_path, font_size)
Then render the font using fontObj.render and blit to a surface as in veiset's answer above. :)
I have some code in my game that displays live score. It is in a function for quick access.
def texts(score):
font=pygame.font.Font(None,30)
scoretext=font.render("Score:"+str(score), 1,(255,255,255))
screen.blit(scoretext, (500, 457))
and I call it using this in my while loop:
texts(score)
There are 2 possibilities. In either case PyGame has to be initialized by pygame.init.
import pygame
pygame.init()
Use either the pygame.font module and create a pygame.font.SysFont or pygame.font.Font object. render() a pygame.Surface with the text and blit the Surface to the screen:
my_font = pygame.font.SysFont(None, 50)
text_surface = myfont.render("Hello world!", True, (255, 0, 0))
screen.blit(text_surface, (10, 10))
Or use the pygame.freetype module. Create a pygame.freetype.SysFont() or pygame.freetype.Font object. render() a pygame.Surface with the text or directly render_to() the text to the screen:
my_ft_font = pygame.freetype.SysFont('Times New Roman', 50)
my_ft_font.render_to(screen, (10, 10), "Hello world!", (255, 0, 0))
See also Text and font
Minimal pygame.font example: repl.it/#Rabbid76/PyGame-Text
import pygame
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 100)
text = font.render('Hello World', True, (255, 0, 0))
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
window.blit(text, text.get_rect(center = window.get_rect().center))
pygame.display.flip()
pygame.quit()
exit()
Minimal pygame.freetype example: repl.it/#Rabbid76/PyGame-FreeTypeText
import pygame
import pygame.freetype
pygame.init()
window = pygame.display.set_mode((500, 150))
clock = pygame.time.Clock()
ft_font = pygame.freetype.SysFont('Times New Roman', 80)
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (128, 128, 128), (64, 64, 64)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
text_rect = ft_font.get_rect('Hello World')
text_rect.center = window.get_rect().center
ft_font.render_to(window, text_rect.topleft, 'Hello World', (255, 0, 0))
pygame.display.flip()
pygame.quit()
exit()
I wrote a wrapper, that will cache text surfaces, only re-render when dirty. googlecode/ninmonkey/nin.text/demo/
I wrote a TextBox class. It can use many custom fonts relatively easily and specify colors.
I wanted to have text in several places on the screen, some of which would update such as lives, scores (of all players) high score, time passed and so on.
Firstly, I created a fonts folder in the project and loaded in the fonts I wanted to use. As an example, I had 'arcade.ttf' in my fots folder. When making an instance of the TextBox, I could specify that font using the fontlocation (optional) arg.
e.g.
self.game_over_text = TextBox("GAME OVER", 100, 80, 420, RED, 'fonts/arcade.ttf')
I found making the text and updating it each time "clunky" so my solution was an update_text method.
For example, updating the Player score:
self.score1_text.update_text(f'{self.p1.score}')
It could be refactored to accept a list of str, but it suited my needs for coding a version of "S
# -*- coding: utf-8 -*-
'''
#author: srattigan
#date: 22-Mar-2022
#project: TextBox class example
#description: A generic text box class
to simplify text objects in PyGame
Fonts can be downloaded from
https://www.dafont.com/
and other such sites.
'''
# imports
import pygame
# initialise and globals
WHITE = (255, 255, 255)
pygame.font.init() # you have to call this at the start
class TextBox:
'''
A text box class to simplify creating text in pygame
'''
def __init__(self, text, size, x=50, y=50, color=WHITE, fontlocation=None):
'''
Constuctor
text: str, the text to be displayed
size: int, the font size
x: int, x-position on the screen
y: int, y-position on the screen
color: tuple of int representing color, default is (255,255,255)
fontlocation: str, location of font file. If None, default system font is used.
'''
pygame.font.init()
self.text = text
self.size = size
self.color = color
self.x = x
self.y = y
if fontlocation == None:
self.font = pygame.font.SysFont('Arial', self.size)
else:
self.font = pygame.font.Font(fontlocation, self.size)
def draw(self, screen):
'''
Draws the text box to the screen passed.
screen: a pygame Surface object
'''
text_surface = self.font.render(f'{self.text}', False, self.color)
screen.blit(text_surface, [self.x, self.y])
def update_text(self, new_text):
'''
Modifier- Updates the text variable in the textbox instance
new_text: str, the updated str for the instance.
'''
if not isinstance(new_text, str):
raise TypeError("Invalid type for text object")
self.text = new_text
def set_position(self, x, y):
'''
Modifier- change or set the position of the txt box
x: int, x-position on the screen
y: int, y-position on the screen
'''
self.x = x
self.y = y
def __repr__(self):
rep = f'TextBox instance, \n\ttext: {self.text} \n\tFontFamly:{self.font} \n\tColor: {self.color} \n\tSize: {self.size} \n\tPos: {self.x, self.y}'
return rep
if __name__ == "__main__":
test = TextBox("Hello World", 30, 30, 30)
print(test)
To use this in my Game class
from textbox import TextBox
and in the initialisation part of the game, something like this:
self.time_text = TextBox("Time Left: 100", 20, 20, 40)
self.cred_text = TextBox("created by Sean R.", 15, 600, 870)
self.score1_text = TextBox("0", 100, 40, 650)
self.score2_text = TextBox("0", 100, 660, 650)
self.lives1_text = TextBox("[P1] Lives: 3", 20, 40, 750)
self.lives2_text = TextBox("[P2] Lives: 3", 20, 660, 750)
self.game_over_text = TextBox("GAME OVER", 100, 80, 420, RED)
self.textbox_list = []
self.textbox_list.append(self.time_text)
self.textbox_list.append(self.cred_text)
self.textbox_list.append(self.score1_text)
self.textbox_list.append(self.score2_text)
self.textbox_list.append(self.lives1_text)
self.textbox_list.append(self.lives2_text)
so that when I want to draw all on the screen:
for txt in self.textbox_list:
txt.draw(screen)
In the update section of the game, I only update directly the boxes that have updated text using the update_text method- if there is nothing to be updated, the text stays the same.
I wrote a TextElement class to handle text placement. It's still has room for improvement. One thing to improve is to add fallback fonts using SysFont in case the font asset isn't available.
import os
from typing import Tuple, Union
from pygame.font import Font
from utils.color import Color
class TextElement:
TEXT_SIZE = 50
def __init__(self, surface, size=TEXT_SIZE, color=Color('white'), font_name='Kanit-Medium') -> None:
self.surface = surface
self._font_name = font_name
self._size = size
self.color = color
self.font = self.__initialize_font()
#property
def font_name(self):
return self._font_name
#font_name.setter
def font_name(self, font_name):
self._font_name = font_name
self.font = self.__initialize_font()
#font_name.deleter
def font_name(self):
del self._font_name
#property
def size(self):
return self._size
#size.setter
def size(self, size):
self._size = size
self.font = self.__initialize_font()
#size.deleter
def size(self):
del self._size
def write(self, text: str, coordinates: Union[str, Tuple[int, int]] = 'center'):
rendered_text = self.font.render(text, True, self.color)
if isinstance(coordinates, str):
coordinates = self.__calculate_alignment(rendered_text, coordinates)
self.surface.blit(rendered_text, coordinates)
return self
def __calculate_alignment(self, rendered_text, alignment):
# https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_rect
# Aligns rendered_text to the surface at the given alignment position
# e.g: rendered_text.get_rect(center=self.surface.get_rect().center)
alignment_coordinates = getattr(self.surface.get_rect(), alignment)
return getattr(rendered_text, 'get_rect')(**{alignment: alignment_coordinates}).topleft
def __initialize_font(self):
return Font(os.path.join(
'assets', 'fonts', f'{self._font_name}.ttf'), self._size)
Here is how you can use it:
TextElement(self.screen, 80).write('Hello World!', 'midtop')
TextElement(self.screen).write('Hello World 2!', (250, 100))
# OR
text = TextElement(self.screen, 80)
text.size = 100
text.write('Bigger text!', (25, 50))
text.write('Bigger text!', 'midbottom')
I hope this can help someone! Cheers!

Resources