Welcome, guest | Sign In | My Account | Store | Cart

This module was inspired by http://processing.org/ . The code is meant to mimic an extremely small subset of this high-performance library. After seeing how easy it was to accomplish certain task in the Java framework, a desire was created to have a simple starting point to create little graphical experiments that would be easy to program without haveing to know a lot of details regarding setup, timing, et cetera. This is the result of that desire.

Python, 140 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import itertools
import time
import tkinter
import vector

################################################################################

class Polygon:

    "Polygon(*vertices) -> Polygon"

    __slots__ = 'vertices'

    def __init__(self, *vertices):
        "Initializes polygon with a list of vertices."
        self.vertices = vertices

    def translate(self, offset):
        "Moves the polygon by the specified offset."
        for vertex in self.vertices:
            vertex += offset

    def rotate(self, direction):
        "Rotates the polygon by a vector's direction."
        for vertex in self.vertices:
            vertex.direction += direction

    def scale(self, factor):
        "Increases or decreases size of the polygon."
        for vertex in self.vertices:
            vertex *= factor

    def copy(self):
        "Copies the polygon by copying its vertices."
        return Polygon(*(vertex.copy() for vertex in self.vertices))

################################################################################

class Graphics:

    "Graphics(canvas) -> Graphics"

    __slots__ = 'canvas'

    def __init__(self, canvas):
        "Initializes graphics by wrapping a canvas."
        self.canvas = canvas

    def draw(self, polygon, fill, outline):
        "Draws a polygon on the underlying canvas."
        self.canvas.create_polygon(*itertools.chain(*polygon.vertices),
                                   fill=fill, outline=outline)

    def write(self, x, y, text, fill):
        "Writes the text to bottom-left of location."
        self.canvas.create_text(x, y, text=text, fill=fill, anchor=tkinter.NW)

    def clear(self):
        "Clears canvas of all objects shown on it."
        self.canvas.delete(tkinter.ALL)

    def fill(self, background):
        "Fills in the canvas with the given color."
        self.canvas.configure(background=background)

################################################################################

class Process(tkinter.Frame):

    "Process(master, width, height) -> Process"

    FRAMESKIP = 5   # How many frames should skip before issuing speed warning?
    RENDER_EACH_SECOND = 15 # How often should the DISPLAY be updated / second?
    UPDATE_EACH_SECOND = 30 # How often should the PHYSICS be updated / second?

    @classmethod
    def main(cls, width, height):
        "Creates a process in a window and executes it."
        tkinter.NoDefaultRoot()
        root = tkinter.Tk()
        root.title('Processing 1.1')
        root.resizable(False, False)
        view = cls(root, width, height)
        view.grid()
        root.mainloop()

    ########################################################################

    def __init__(self, master, width, height):
        "Initializes process and starts simulation loops."
        super().__init__(master)
        canvas = tkinter.Canvas(self, width=width, height=height,
                                background='white', highlightthickness=0)
        canvas.bind('<1>', self.mouse_pressed)
        canvas.grid()
        graphics = Graphics(canvas)
        self.setup(graphics.fill)
        render = 1 / self.RENDER_EACH_SECOND
        update = 1 / self.UPDATE_EACH_SECOND
        self.__loop(lambda: self.render(graphics), render, time.clock(), 0)
        self.__loop(lambda: self.update(update), update, time.clock(), 0)

    def __loop(self, method, interval, target, errors):
        "Runs the method after each interval has passed."
        method()
        target += interval
        ms = int((target - time.clock()) * 1000)
        if ms >= 0:
            self.after(ms, self.__loop, method, interval, target, 0)
        elif errors == self.FRAMESKIP:
            self.speed_warning()
            self.after_idle(self.__loop, method, interval, target, 0)
        else:
            self.after_idle(self.__loop, method, interval, target, errors + 1)

    ########################################################################
    
    def setup(self, background):
        "This method is called before render and update."
        raise NotImplementedError()

    def render(self, graphics):
        "This method is called when screen should render."
        raise NotImplementedError()

    def update(self, interval):
        "This method is called when physics should update."
        raise NotImplementedError()

    def mouse_pressed(self, event):
        "This method is called when left-clicking mouse."
        raise NotImplementedError()

    def speed_warning(self):
        "This method is called when code is running slow."
        raise NotImplementedError()

################################################################################

import recipe576904; recipe576904.bind_all(globals())