Related
I have made a Button class that basically draws a Rect object and a text if specified and I implemented an animation that basically shrinks the button and inflates it back quickly on click
class Button:
def __init__(self, surface, pos, size, bg_color, hover_color):
self.surface = surface
self.x, self.y = pos[0], pos[1]
self.width, self.height = size[0], size[1]
self.bgColor = bg_color
self.hoverColor = hover_color
self.rect = pygame.Rect(pos, size)
self.text = None
self.clicked = False
def addText(self, font, text, txt_color):
self.text = font.render(text, True, txt_color)
self.textRect = self.text.get_rect(center = self.rect.center)
def update(self):
if self.isHovered():
pygame.draw.rect(self.surface, self.hoverColor, self.rect, border_radius=20)
else:
pygame.draw.rect(self.surface, self.bgColor, self.rect, border_radius=20)
if(self.text):
self.surface.blit(self.text, self.textRect)
self.checkClicked()
def checkClicked(self):
if(self.rect.collidepoint(pygame.mouse.get_pos())):
if(pygame.mouse.get_pressed()[0]):
if(not self.clicked):
self.clicked = True
self.rect.inflate_ip(-7, -7)
else:
if(self.clicked):
self.clicked = False
self.rect.inflate_ip(7, 7)
def isHovered(self):
return True if self.rect.collidepoint(pygame.mouse.get_pos()) else False
It's probably not the most effecient way to do it, but I'll worry about that later.
The implementation then would be :
startBtn = Button(display, (100, 160), (200, 40), (40, 40, 40), (70, 70, 70))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
if(startBtn.rect.collidepoint(pygame.mouse.get_pos())):
showNextScreen() # Here lies the issue
print('pressed')
display.fill((50, 50, 50))
startBtn.update()
pygame.display.update()
I have a function that would draw a new screen and bascially starts the game, however it happens too quick that you can't see the animation of the button and I am struggling to find a way to let the script wait for the button animation to finish then do stuff, I have tried pygame.time.wait() and pygame.time.delay() but the whole script freezes and makes it worse, How could I make that happen?
You could just use time.sleep()
I wrote a function called sleep that waits a given amount of time without freezing the game.
Function
def sleep(ms): # ms --> time in milliseconds
start = pygame.time.get_ticks()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
now = pygame.time.get_ticks()
if now - start == ms:
running = False
Explanation
It takes one parameter: ms, which is the amount of time that you want the program to wait in milliseconds (1 second = 1000 milliseconds). In the function, I set a variable called start to pygame.time.get_ticks(), which returns the current time. Then, inside a while loop, I created an event loop which checks for the pygame.QUIT event so that if the user clicks X while the function is still running, the program will respond and quit the program. After the event loop, I set a variable called now to pygame.time.get_ticks() to get the current time. Then I checked if now (the current time) subtracted by start (the start time) is equal to ms (the given amount of waiting time in milliseconds). This checks if the given amount of time has passed. If it has, the while loop ends. If not, the while loop keeps running until that condition is True.
I have found the way to achieve this, again it might not be the optimal way to do it but it does the job :
Importing Timer from threading to create a timer and passing the function that would draw the next screen/scene as an argument to delay it for some milliseconds does the trick.
#up in the script
from threading import Timer
import pygame
#then some code goes here
.
.
.
def showNextScreen():
#code related to the next screen goes here
startBtn = Button(display, (100, 160), (200, 40), (40, 40, 40), (70, 70, 70))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
if(startBtn.rect.collidepoint(pygame.mouse.get_pos())):
t = Timer(0.3, showNextScreen) #Timer(seconds, function)
t.start() #starts the timer and once its over it calls the function passed
#giving the button animation enough time to fully finish.
print('pressed')
display.fill((50, 50, 50))
startBtn.update()
pygame.display.update()
I have a question, its more an OS-based one.
I'm playing a video game and I want to be able to put a textual timer ontop of the game's screen as if it was a part of the game itself.
Now, I can write a program in any language that displays a TextBox with a timer on the screen, but if I run it, the game's process (lets call it game.exe) "loses" its focus and I get my TextBox focused and interactive by the OS.
Is there any option to display that text "ontop" of the game.exe that comes from an entire different process? as if there were "layers" to the screen. Also, this text shouldn't be intractable, clickable or make the game.exe process lose its focus.
Here's a very simple example I drew:
Thanks a lot!
Solved this using a window trick with python and tkinter with some windows api stuff.
The trick is to create a transparent non-clickable window and keep it always on top.
I've basically combined this answer with a bunch of simpler stuff like removing window's border and set to auto fullscreen.
from tkinter import *
import time
import win32gui
import win32api
from win32api import GetSystemMetrics
# WIDTH = 500
# HEIGHT = 500
WIDTH = GetSystemMetrics(0)
HEIGHT = GetSystemMetrics(1)
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
# tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
tk.attributes('-fullscreen', True)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, highlightthickness=0)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
canvas.create_text(WIDTH/2,HEIGHT/2,fill="white",font="Arial 20", text="TEXT GOES HERE")
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))
Ok, so I've been tasked with creating a VERY simple GUI at work (I'm an intern). The task is to eventually connect to a machine and process real data, but right now I'm working on randomly generated sine data with noise. I've chose to work in Python 3.0, and use wxpython to create my GUI components.
As I want everything to appear on the same window, I'm using panels (hence wx.lib.plot.PlotCanvas rather than something like matplotlib.pyplot)
The problem that I have is that over time, the plot seems to 'expand' off of the panel. This is temporarily solved when I manually resize the window, but resumes again immediately after (you need to run the code to see what I mean).
Expansion over time in panel
Another problem (that has bugged me since I have started writing the code) is that sometimes when I resize the window (manually) or minimize it and then maximize it again, the timer randomly starts and stops.
I have tried all sorts of things (changing padding in sizers, extra arguments, changing time between refreshes GetBestSize()) but I believe that I simply don't understand wxpython well enough to identify where the problem is
I would really appreciate any help you can shed on either of these problems (I don't know, they might even be linked to each other).
FYI: I am not an experienced coder, and my code is not finished (I have more functions to code, but I feel like I should resolve this first). I have constructed this code by looking at different techniques from various tutorials and websites like stackoverflow, so I know it's not formatted well and could definitely be made more efficient. Also, I have removed some parts just to be safe about confidentiality - nothing important, just strings in messages.
PS: If you do have an easier way to do the whole plot/update thing that doesn't have this problem (preferably still in wx) I would be thrilled to hear that as well
And here's my code:
EDIT: Solved the expanding problem by using self.p2.SetSize((W+0,L+0)) instead of (self.p2.GetBestSize())
EDIT: Made transitions much smoother by just regenerating data and redrawing it on existing canvas in the evt_timer function (instead of recreating the whole canvas, which gave a blink-y appearance if you know what I mean)
import wx
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import leastsq
import wx.lib.plot as plot
import time
import os
wildcard = "Text File (*.txt)|*.txt|"\
"Picture (*.png)|*.png|"\
"All files (*.*)|*.*"#This wildcard shows the options for file endings in the "SAVE" tab - see OnSave(self,event)
wildcard2 = "Picture (*.png)|*.png|"\
"Text File (*.txt)|*.txt|"\
"All files (*.*)|*.*"
class PlotCanvas(plot.PlotCanvas):
def __init__(self,parent,id,size,accepted):
"""
This randomly generates sine data (with noise) and plots it to a panel.
Incorporated as a separate class instead of instatiating it as a plot.PlotCanvas object
to overcome an issue of the size of the plot in the panel.
"""
plot.PlotCanvas.__init__(self,parent,id,style=wx.BORDER_SUNKEN,size = size)
N = 100 # number of data points
self.t = np.linspace(0, 4*np.pi, N)
f = 1.15247 # Optional!! Advised not to use
self.data = 3.0*np.sin(f*self.t+0.001) + 0.5 + np.random.randn(N) # create artificial data with noise
guess_mean = np.mean(self.data)
guess_phase = 0
guess_freq = 1
guess_amp = 1
optimize_func = lambda x: x[0]*np.sin(x[1]*self.t+x[2]) + x[3] - self.data
est_amp, est_freq, est_phase, est_mean = leastsq(optimize_func, [guess_amp, guess_freq, guess_phase, guess_mean])[0]
fine_t = np.arange(0,max(self.t),0.1)
data_fit=est_amp*np.sin(est_freq*fine_t+est_phase)+est_mean
multiplier = 1
dataset1 = [(x,[d for d in self.data][[td for td in self.t].index(x)])for x in [td for td in self.t]]
fitdata1 = [(x,[df for df in data_fit][[tf for tf in fine_t].index(x)]) for x in [tf for tf in fine_t]]
dataset =[(x,y*multiplier) for (x,y) in dataset1]
fitdata = [(x,y*multiplier) for (x,y) in fitdata1]
self.data = dataset
self.data2 = fitdata
line = plot.PolyLine(self.data,legend = 'random',colour = 'light blue', width =2)
line2 = plot.PolyLine(self.data2,legend = 'sineline',colour ='black',width =2)
a = []
if "D" in accepted:
a.append(line)
if "S" in accepted:
a.append(line2)
if "G" in accepted:
pass
if "L" in accepted:
pass
gc = plot.PlotGraphics(a,'Line Graph','X','Y')
xmin = self.t[0]-0.01*(self.t[-1]-self.t[0])
xmax = self.t[-1]+0.01*(self.t[-1]-self.t[0])
self.Draw(gc,xAxis=(xmin,xmax),yAxis=(min([x[1] for x in dataset])-0.01*(max([x[1] for x in dataset])-min([x[1] for x in dataset])),
max([x[1] for x in dataset])+0.01*(max([x[1] for x in dataset])-min([x[1] for x in dataset]))))
#self.showLegend = True
#self.enableZoom = True
def Dialog(self, parent, message, c):# Will be used to notify the user of errors/processes
if c == "W":
caption = "Warning!"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_WARNING)
elif c == "I":
caption = "Information"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()#Destroys dialog on close
class Frame(wx.Frame):
"""
This is the main class. In it, we declare the separate panels, canvas, menubar, buttons and sizers.
"""
def __init__(self,parent,id,title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition)
self.CurrentDirectory = os.getcwd()
self.timer=wx.Timer(self)#Instantiating the timer
self.count=0
self.Bind(wx.EVT_TIMER,self.evt_timer)#Binding it to itself so that it is always triggered
self.Bind(wx.EVT_PAINT,self.paint)
menubar = wx.MenuBar()
fileMenu = wx.Menu() #Creating the Menubar at the top
#Creating 3 menus: fileMenu,fit,and help
save = wx.Menu()
z = wx.MenuItem(save,wx.ID_ANY,'Save Raw Data\tCtrl+D')
self.Bind(wx.EVT_MENU,self.OnSave,z)
save.Append(z)
z= wx.MenuItem(save,wx.ID_ANY,'Save Image\tCtrl+I')
self.Bind(wx.EVT_MENU,self.OnSaveImage,z)
save.Append(z)
fileMenu.AppendSubMenu(save,'&Save')
fileMenu.AppendSeparator()
z = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit\tCtrl+W')
self.Bind(wx.EVT_MENU, self.OnQuit, z)
fileMenu.Append(z)
fit = wx.Menu()#Making a check menu
self.gaussian = fit.Append(wx.ID_ANY,'Gaussian',kind = wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleGaussian,self.gaussian)
fit.Check(self.gaussian.GetId(),False)
self.sine = fit.Append(wx.ID_ANY,'Sine',kind = wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleSine,self.sine)
fit.Check(self.sine.GetId(),False)
self.linear = fit.Append(wx.ID_ANY,'Linear',kind=wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleLinear,self.linear)
fit.Check(self.linear.GetId(),False)
help = wx.Menu()
z = wx.MenuItem(help,wx.ID_ANY,'&About\tCtrl+H')
self.Bind(wx.EVT_MENU,self.OnHelp,z)
help.Append(z)
menubar.Append(fileMenu, '&File')
menubar.Append(fit, '&Fit')
menubar.Append(help, '&Help')#adding menus to menubar
self.SetMenuBar(menubar)#formatting the frame with menubar
self.sp = wx.SplitterWindow(self)#Splitting the window into 2 panels
self.p1 = wx.Panel(self.sp,style = wx.SUNKEN_BORDER)#For buttons and user events
self.p2 = wx.Panel(self.sp,style = wx.SUNKEN_BORDER)#For display of the plot
self.sp.SplitVertically(self.p1,self.p2,300)
sizer = wx.GridBagSizer(3, 3)#Versatile sizer for layout of first panel self.p1
bitmappath = self.CurrentDirectory + "\\BITMAPS"
bmp = wx.Bitmap(bitmappath+"\\SAVE.BMP",wx.BITMAP_TYPE_BMP)
self.saveBtn = wx.BitmapButton(self.p1,wx.ID_ANY,bitmap = bmp,size =(bmp.GetWidth()+10,bmp.GetHeight()+10))
self.Bind(wx.EVT_BUTTON,self.OnSave,self.saveBtn)
sizer.Add(self.saveBtn, (0, 0), wx.DefaultSpan, wx.ALL,5)
bmp = wx.Bitmap(bitmappath +"\\START.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn = wx.BitmapButton(self.p1,-1,bitmap = bmp,size =(bmp.GetWidth()+10,bmp.GetHeight()+10))# A button that starts and stops the plotting
self.startBtn.startval = "START"
self.Bind(wx.EVT_BUTTON,self.paint,self.startBtn)
sizer.Add(self.startBtn, (0, 1), wx.DefaultSpan,wx.ALL,5)
sizer1 = wx.BoxSizer(wx.VERTICAL)
W,L = self.p2.GetSize()
self.p2.canvas = PlotCanvas(self.p2,wx.ID_ANY,(W,L),["D"])
sizer1.Add(self.p2.canvas,1,wx.ALL,0,0)
self.p2.SetSizerAndFit(sizer1)
self.p1.SetSizerAndFit(sizer)
self.p2.SetSizerAndFit(sizer1)
self.p2.SetSize(W,L)
self.Maximize(True)
self.Centre()
self.Show()
############### event methods ###########
def paint(self,event):
"""
Updates the canvas based on the value of the startbtn(not the image). Bound to self.timer.
"""
bitmappath = self.CurrentDirectory + "\\BITMAPS"
if self.startBtn.startval == "START":
self.timer.Start(1)# increase the value for more time
bmp = wx.Bitmap(bitmappath + "\\STOP.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn.SetBitmap(bmp)
self.startBtn.startval = "STOP"
elif self.startBtn.startval == "STOP":
self.timer.Stop()
bmp = wx.Bitmap(bitmappath+ "\\START.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn.SetBitmap(bmp)
self.startBtn.startval = "START"
def evt_timer(self,event):
self.count +=1
if self.count== 10:# By increasing count (or the number in self.timer.Start()) you can increase the interval between updates
#self.p2.canvas.Clear()
sizer1 = wx.BoxSizer(wx.VERTICAL)
W,L = self.p2.GetSize()
a = ["D"]
if self.sine.IsChecked():
a.append("S")
elif self.linear.IsChecked():
a.append("L")
elif self.gaussian.IsChecked():
a.append("G")
self.p2.canvas = PlotCanvas(self.p2,wx.ID_ANY,(W,L),a)
sizer1.Add(self.p2.canvas,1,wx.ALL,0,0)
self.p2.SetSizerAndFit(sizer1)
self.p2.SetSize(self.p2.GetBestSize())
self.count=0 # reset the count
def Dialog(self, parent, message, c):# Will be used to notify the user of errors/processes
if c == "W":
caption = "Warning!"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_WARNING)
elif c == "I":
caption = "Information"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()#Destroys dialog on close
def OnSave(self,event):#Triggered by menubar and button
try:
rawdata = self.p2.canvas.data
raw_X =[x[0] for x in rawdata]
raw_Y =[x[1] for x in rawdata]
dlg = wx.FileDialog(#Code for this from http://www.blog.pythonlibrary.org
self, message="Save file as ...",
defaultDir=self.CurrentDirectory,
defaultFile=str(time.ctime()), wildcard=wildcard, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
f = open(path+".txt","w+")
for i in range(len(raw_X)):
f.write(str(raw_X[i])+"\t"+str(raw_Y[i])+"\n")
f.close()
self.Dialog(None,"File successfully saved","I")
except UnboundLocalError:#Catch error when user closes save window without selecting any directory or filename
pass
def OnSaveImage(self,event):
try:
rawdata = self.p2.canvas.data
raw_X = [x[0] for x in rawdata]
raw_Y = [x[1] for x in rawdata]
dlg = wx.FileDialog(
self, message="Save file as ...",
defaultDir=self.CurrentDirectory,
defaultFile=str(time.ctime()), wildcard=wildcard2, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
fig1 = plt.figure()
plt.plot(raw_X,raw_Y)
plt.title("Raw Data")
fig1.savefig(path+".png")
self.Dialog(None,"File successfully saved","I")
except UnboundLocalError:
pass
def OnMultiply(self,e):
try:
factor = self.x.GetValue()
factor = float(factor)
self.IntegrationTime = factor
except ValueError as e:
self.Dialog(None,str(e),"W")
def OnQuit(self, e):
self.Close()
def OnHelp(self,e):
self.Dialog(None,"N/A","I")
def ToggleSine(self,e):
pass
def ToggleLinear(self,e):
self.Dialog(None,"Not added yet","W")
def ToggleGaussian(self,e):
self.Dialog(None,"Not added yet","W")
if __name__ =="__main__":
app=wx.App()
Frame(None,-1,"N/A")
app.MainLoop()
I'm trying to teach myself the tkinter module by programming minesweeper. I have created a grid with buttons and a method to set an image flag to cells. It works, in that when you press the right mouse button the image of the button changes as desired, but when you right click on the next button the image just moves to the next button, rather than creating a second flag. I want to be able to place a new flag image on each cell that I right click, rather than just shuffle the image around. Here's my code:
import tkinter as Tk
def main():
root = Tk.Tk()
root.geometry('{}x{}'.format(700, 700))
instance = Minesweeper(root, 10, 10)
root.mainloop()
class Minesweeper:
def __init__(self, parent, height, width):
self.top_frame = Tk.Frame(parent)
self.top_frame.place(anchor=Tk.CENTER, relx=0.5, rely=0.5)
self.frames = []
self.buttons = []
index = 0
for x in range(height):
for y in range(width):
self.frames.append(Tk.Frame(self.top_frame, height=50, width=50))
self.buttons.append(Tk.Button(self.frames[index], bg="white"))
self.frames[index].grid_propagate(False)
self.frames[index].columnconfigure(0, weight=1)
self.frames[index].rowconfigure(0, weight=1)
self.frames[index].grid(row=x, column=y)
self.buttons[index].grid(sticky="wens")
self.buttons[index].bind('<Button-3>', self.flag)
index += 1
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.configure(image=self.flag)
if __name__ == "__main__":
main()
Seems that the below fixed it:
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.image = self.flag # <---- this seemed to fix it
event.widget.configure(image=self.flag)
I'm writing simple GUI using wxPyhon and faced some problems.
My application does simple things: it draws triangle on the form and rotates it when user clicks arrow buttons or drags a mouse cursor over the form.
THe problems I see now are following:
1. Then I drag a mouse sursor fast the triangle rotates with keeping old image visible for a short time. When keeping moving a cursor fast for a while the drawing on the form is looking like 2 or 3 triangles.
2. If I expand the form to entire size of the screen the triangle moves unsmoothly, with small jumps from old appearance to a new one. I looked at coordinates of a mouse cursor during that rotating and noticed that they are tracked with gaps. Friend of mine said me that it is because I redraw the entire window of the application every time I wand to rotate the triangle a little bit. And that's why it works slowly and it slow down the tracking of a mouse cursor.
To refresh the view I'm using wx.Panel.Refresh() method. As drawing context I'm using wx.BufferedDC()
Please tell me how to draw CORRECTLY dynamicaly changing pictures/drawings on the wxPython forms, especially the way I make in that application.
I could place my code here, but it's too long. So if I must tell something more about my case - ask me please, I will answer.
Thanks !
class SimpleGraphics(wx.Panel):
def __init__(self, parent, size=(50, 50)):
super(SimpleGraphics, self).__init__(parent,
size=size,
style=wx.NO_BORDER)
self.color = "Black"
self.thickness = 2
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
self.MARGIN = 1 #px
self.points = [[0.0, 0.5], [0.5, 0.0], [-0.5, -0.5]]
self.pos = (0, 0)
self.cur_vector = Vector2D(1, 1)
self.InitBuffer()
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyArrow)
# MOUSE TRACKING
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def InitBuffer(self):
self.client_size = self.GetClientSize()
self.buffer = wx.EmptyBitmap(self.client_size.width, self.client_size.height)
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawImage(dc)
self.reInitBuffer = False
def OnSize(self, event):
self.reInitBuffer = True
def repaint_the_view(self):
self.InitBuffer()
self.Refresh()
def OnIdle(self, event):
if self.reInitBuffer:
self.repaint_the_view()
def OnKeyArrow(self, event):
key_code = event.GetKeyCode()
if key_code == wx.WXK_LEFT:
self.rotate_points(degrees_to_rad(5))
elif key_code == wx.WXK_RIGHT:
self.rotate_points(degrees_to_rad(-5))
self.repaint_the_view()
event.Skip()
def OnLeftDown(self, event):
# get the mouse position and capture the mouse
self.pos = event.GetPositionTuple()
self.cur_vector = create_vector2d(self.pos[0], self.pos[1],
self.client_size.width / 2,
self.client_size.height / 2)
self.CaptureMouse()
def OnLeftUp(self, event):
#release the mouse
if self.HasCapture():
self.ReleaseMouse()
def OnMotion(self, event):
if event.Dragging() and event.LeftIsDown():
newPos = event.GetPositionTuple()
new_vector = create_vector2d(newPos[0], newPos[1],
self.client_size.width / 2,
self.client_size.height / 2)
if new_vector.lenth() > 0.00001:
c = cos_a(self.cur_vector, new_vector)
s = sin_a(self.cur_vector, new_vector)
rot_matr = rotation_matrix(s, c)
self.rotate_points(rot_matr=rot_matr)
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) # this line I've added after posting the question
self.repaint_the_view()
self.cur_vector = new_vector
event.Skip()
def OnPaint(self, event):
wx.BufferedPaintDC(self, self.buffer)
def DrawImage(self, dc):
dc.SetPen(self.pen)
new_points = self.convetr_points_to_virtual()
dc.DrawPolygon([wx.Point(x, y) for (x, y) in new_points])
def to_x(self, X_Log):
X_Window = self.MARGIN + (1.0 / 2) * (X_Log + 1) * (self.client_size.width - 2 * self.MARGIN)
return int(X_Window)
def to_y(self, Y_Log):
Y_Window = self.MARGIN + (-1.0 / 2) * (Y_Log - 1) * (self.client_size.height - 2 * self.MARGIN)
return int(Y_Window)
def convetr_points_to_virtual(self):
return [(self.to_x(x), self.to_y(y)) for (x, y) in self.points]
def rotate_points(self, angle_in_degrees=None, rot_matr=None):
if angle_in_degrees is None:
self.points = [rotate_point(x, y , rotator_matrix=rot_matr) for (x, y) in self.points]
else:
self.points = [rotate_point(x, y , angle_in_degrees) for (x, y) in self.points]
class SimpleGraphicsFrame(wx.Frame):
def __init__(self, parent, *args, **kwargs):
wx.Frame.__init__(self, parent, *args, **kwargs)
# Attributes
self.panel = SimpleGraphics(self)
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
class SimpleGraphApp(wx.App):
def OnInit(self):
self.frame = SimpleGraphicsFrame(None,
title="Drawing Shapes",
size=(300, 400))
self.frame.Show()
return True
if __name__ == '__main__':
app = SimpleGraphApp(False)
app.MainLoop()
You call self.Refresh() from your OnKeyArrow and OnMotion events. Update your scene data in those methods and set some flag e.g. self.repaint_needed = True. Then in OnIdle repaint the scene if self.repaint_needed is True.
Now you try to repaint the window every time the event is received. Which may be a lot.
What you want to do is to update the scene information every time, but repaint the window only when wx indicates it has some "free time".