1
0
mirror of https://github.com/rzzzwilson/pymlac.git synced 2025-06-10 09:32:41 +00:00

Moving to a wx widget for Display

This commit is contained in:
Ross Wilson
2015-06-18 13:20:08 +07:00
parent f87dd8a10f
commit 955455af70
2 changed files with 421 additions and 18 deletions

View File

@@ -1,48 +1,334 @@
#!/usr/bin/python
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
The Imlac display.
"""
import pygame
from pygame.locals import *
import wx
from Globals import *
# if we don't have log.py, don't crash
try:
import log
log = log.Log('test.log', log.Log.DEBUG)
except ImportError:
def log(*args, **kwargs):
pass
class Display(object):
# the widget version
__version__ = '0.0'
# type of events
DisplayStop = 1
######
# Base class for the widget canvas - buffered and flicker-free.
######
class _BufferedCanvas(wx.Panel):
"""Implements a buffered, flicker-free canvas widget.
This class is based on:
http://wiki.wxpython.org/index.cgi/BufferedCanvas
"""
# The backing buffer
buffer = None
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_FULL_REPAINT_ON_RESIZE):
"""Initialise the canvas.
parent reference to 'parent' widget
id the unique widget ID (NB: shadows builtin 'id')
pos canvas position
size canvas size
style wxPython style
"""
wx.Panel.__init__(self, parent, id, pos, size, style)
# Bind events
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SIZE, self.OnSize)
# Disable background erasing (flicker-licious)
def disable_event(*args, **kwargs):
pass # the sauce, please
self.Bind(wx.EVT_ERASE_BACKGROUND, disable_event)
# set callback upon onSize event
self.onSizeCallback = None
self.dc = None
def Draw(self, dc):
"""Stub: called when the canvas needs to be re-drawn."""
# set display scale
scaleX = self.view_width*1.0 / self.ScaleMaxX
scaleY = self.view_height*1.0 / self.ScaleMaxY
dc.SetUserScale(min(scaleX,scaleY), min(scaleX,scaleY))
# don't REPLACE this method, EXTEND it!
def Update(self):
"""Causes the canvas to be updated."""
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
dc.BeginDrawing()
dc.Clear() # because maybe view size > map size
self.Draw(dc)
dc.EndDrawing()
self.dc = dc
def OnPaint(self, event):
"""Paint the canvas to the screen."""
# Blit the front buffer to the screen
wx.BufferedPaintDC(self, self.buffer)
def OnSize(self, event=None):
"""Create a new off-screen buffer to hold drawn data."""
(width, height) = self.GetClientSizeTuple()
minsize = min(width, height)
self.SetSize((minsize, minsize))
self.view_width = minsize
self.view_height = minsize
# new off-screen buffer
self.buffer = wx.EmptyBitmap(minsize, minsize)
# call onSize callback, if registered
if self.onSizeCallback:
self.onSizeCallback()
# Now update the screen
self.Update()
class Display(_BufferedCanvas):
BackgroundColour = 'black'
DrawColour = '#ffff00'
SYNC_HZ = 40
SYNC_40HZ_CYCLE_COUNT = int(CYCLES_PER_SECOND / SYNC_HZ)
def __init__(self, screen):
self.screen = screen
# max coorcinates of pymlac display
ScaleMaxX = 1024
ScaleMaxY = 1024
def __init__(self, parent, **kwargs):
# create and initialise the base panel
_BufferedCanvas.__init__(self, parent=parent, **kwargs)
self.SetBackgroundColour(self.BackgroundColour)
# set internal state
self.running = 0
self.cycle_count = 0
self.display = pygame.Surface((1024, 1024))
self.Sync40hz = 1
self.display.fill((0, 0, 0))
self.drawlist = []
def draw(self, dotted, oldx, oldy, x, y):
oldx %= 1024
oldy %= 1024
x %= 1024
y %= 1024
pygame.draw.line(self.display, YELLOW, (oldx, 1024 - oldy), (x, 1024 - y))
self.OnSize()
def flip(self):
self.screen.blit(self.display, (0, 0))
pygame.display.flip()
self.display.fill((0, 0, 0))
def draw(self, x1, y1, x2, y2):
"""Draw a line on the screen.
x1, y1 start coordinates
x2, y2 end coordinates
"""
self.drawlist.append((x1, y1, x2, y2))
self.Update()
def Draw(self, dc):
"""Update the display on the widget screen."""
# there's some code in super.Draw() we need
super(Display, self).Draw(dc)
if self.drawlist:
dc.SetPen(wx.Pen(self.DrawColour))
dc.DrawLineList(self.drawlist)
def syncclear(self):
self.Sync40hz = 0
self.cycle_count = self.SYNC_40HZ_CYCLE_COUNT
@property
def ready(self):
return self.Sync40hz
def tick(self, cycles):
"""Advance the internal state by given cycles."""
self.cycle_count -= cycles
if self.cycle_count <= 0:
self.Sync40hz = 1
self.cycle_count = 0
###############################################################################
# Define the events that are raised by the display widget.
###############################################################################
# display stop
_myEVT_DISPLAY_STOP = wx.NewEventType()
EVT_DISPLAY_STOP = wx.PyEventBinder(_myEVT_DISPLAY_STOP, 1)
class _DisplayEvent(wx.PyCommandEvent):
"""Event sent from the display widget."""
def __init__(self, eventType, id):
"""Construct a display event.
eventType type of event
id unique event number
Event will be adorned with attributes by raising code.
"""
wx.PyCommandEvent.__init__(self, eventType, id)
###############################################################################
# The display widget proper
###############################################################################
class PymlacDisplay(_BufferedCanvas):
"""A widget to display pymlac draw lists."""
# line colour
DrawColour = '#ffff00'
# panel background colour
BackgroundColour = '#000000'
# max coorcinates of pymlac display
ScaleMaxX = 1024
ScaleMaxY = 1024
def __init__(self, parent, **kwargs):
"""Initialise a display instance.
parent reference to parent object
**kwargs keyword args for Panel
"""
# create and initialise the base panel
_BufferedCanvas.__init__(self, parent=parent, **kwargs)
self.SetBackgroundColour(PymlacDisplay.BackgroundColour)
# set some internal state
self.default_cursor = wx.CURSOR_DEFAULT
self.drawlist = None # list of (x1,y1,x2,y2)
# set callback when parent resizes
# self.onSizeCallback = self.ResizeCallback
# force a resize, which sets up the rest of the state
# eventually calls ResizeCallback()
self.OnSize()
######
# GUI stuff
######
def OnKeyDown(self, event):
pass
def OnKeyUp(self, event):
pass
def OnLeftUp(self, event):
"""Left mouse button up.
"""
pass
def OnMiddleUp(self, event):
"""Middle mouse button up. Do nothing in this version."""
pass
def OnRightUp(self, event):
"""Right mouse button up.
Note that when we iterate through the layer_z_order list we must
iterate on a *copy* as the user select process can modify
self.layer_z_order.
THIS CODE HASN'T BEEN LOOKED AT IN A LONG, LONG TIME.
"""
pass
def Draw(self, dc):
"""Do actual widget drawing.
Overrides the _BufferedCanvas.draw() method.
dc device context to draw on
Draws the current drawlist to the screen.
"""
scaleX = self.view_width*1.0 / self.ScaleMaxX
scaleY = self.view_height*1.0 / self.ScaleMaxY
dc.SetUserScale(min(scaleX,scaleY), min(scaleX,scaleY))
if self.drawlist:
dc.SetPen(wx.Pen(self.DrawColour))
dc.DrawLineList(self.drawlist)
######
# Change the drawlist
######
def Drawlist(self, dl):
self.drawlist = dl
self.Update()
######
# Routines for display events
######
def RaiseEventStop(self):
"""Raise a display stop event."""
event = _DisplayEvent(_myEVT_DISPLAY_STOP, self.GetId())
self.GetEventHandler().ProcessEvent(event)
def info(self, msg):
"""Display an information message, log and graphically."""
log_msg = '# ' + msg
length = len(log_msg)
prefix = '#### Information '
banner = prefix + '#'*(80 - len(log_msg) - len(prefix))
log(banner)
log(log_msg)
log(banner)
wx.MessageBox(msg, 'Warning', wx.OK | wx.ICON_INFORMATION)
def warn(self, msg):
"""Display a warning message, log and graphically."""
log_msg = '# ' + msg
length = len(log_msg)
prefix = '#### Warning '
banner = prefix + '#'*(80 - len(log_msg) - len(prefix))
log(banner)
log(log_msg)
log(banner)
wx.MessageBox(msg, 'Warning', wx.OK | wx.ICON_ERROR)

117
pymlac/test_Display.py Executable file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Test the pymlac display widget.
Usage: test_pymlac_display.py [-h]
"""
import wx
import Display
# if we don't have log.py, don't crash
try:
import log
log = log.Log('test.log', log.Log.DEBUG)
except ImportError:
def log(*args, **kwargs):
pass
######
# Various demo constants
######
WindowTitleHeight = 22
DefaultAppSize = (600, 600+WindowTitleHeight)
################################################################################
# The main application frame
################################################################################
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=DefaultAppSize,
title=('Test pymlac display - %s'
% Display.__version__))
self.SetMinSize(DefaultAppSize)
self.panel = wx.Panel(self, wx.ID_ANY)
self.panel.ClearBackground()
# build the GUI
box = wx.BoxSizer(wx.VERTICAL)
self.display = Display.Display(self.panel)
box.Add(self.display, proportion=1, border=1, flag=wx.EXPAND)
self.panel.SetSizer(box)
self.panel.Layout()
self.Centre()
self.Show(True)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.lock = False
self.Refresh()
self.display.draw(0, 0, 1023, 1023)
self.display.draw(1023, 0, 0, 1023)
def OnSize(self, event):
"""Maintain square window."""
if not self.lock:
self.lock = True
(w, h) = event.GetSize()
size = min(w, h)
self.SetSize((size-WindowTitleHeight, size))
self.lock = False
event.Skip()
################################################################################
if __name__ == '__main__':
import sys
import getopt
import traceback
# print some usage information
def usage(msg=None):
if msg:
print(msg+'\n')
print(__doc__) # module docstring used
# our own handler for uncaught exceptions
def excepthook(type, value, tb):
msg = '\n' + '=' * 80
msg += '\nUncaught exception:\n'
msg += ''.join(traceback.format_exception(type, value, tb))
msg += '=' * 80 + '\n'
print msg
sys.exit(1)
# plug our handler into the python system
sys.excepthook = excepthook
# decide which tiles to use, default is GMT
argv = sys.argv[1:]
try:
(opts, args) = getopt.getopt(argv, 'h', ['help'])
except getopt.error:
usage()
sys.exit(1)
for (opt, param) in opts:
if opt in ['-h', '--help']:
usage()
sys.exit(0)
# start wxPython app
app = wx.App()
TestFrame().Show()
app.MainLoop()
sys.exit(0)