def __init__(self, colors=(0,0,0,0), keepTransparent=True, alpha=True): # if not colors: self.colors = alpha t = type(colors) if t is int: self.colors = [rgba(alpha) for i in range(colors)] elif t is bool: self.colors = colors else: if t in (str, pygame.Color) or type(colors[0]) is int: colors = colors, self.colors = [rgba(i) for i in colors] self.n = -1 self.keep = keepTransparent
def __init__(self, colors=(0,0,0,0), keepTransparent=True, alpha=True): # if not colors: self.colors = alpha t = type(colors) if t is int: self.colors = [rgba(alpha) for i in range(colors)] elif t is bool: self.colors = colors else: if t in (str, pygame.Color) or type(colors[0]) is int: colors = colors, self.colors = [rgba(i) for i in colors] self.n = -1 self.keep = keepTransparent
def _tempColor(px, color, *args): "Replace one color with a random RGB color" c = color while c in args: c = rgba(False) if c != color: px.replace(color, c) return c
def onkeydown(self, ev): u = ev.unicode.upper() if u == '?': self.menu() elif u == 'O': self.open() elif u == '\t': self["Text"].config(color=rgba(False)) elif "Video" in self: vid = self["Video"] if u == 'S': self.saveAs() elif u == 'G': fn = ask(asksaveasfilename, filetypes=IMAGETYPES) if fn: vid[vid.costumeNumber].save(fn) elif u == 'F': fps = ask(askfloat, title="Frame Rate", prompt="Enter new frame rate:") if fps: self.frameRate = max(1.0, fps) elif u == ' ': vid.costumeTime = 1 - vid.costumeTime elif ev.key == K_HOME: vid.costumeTime = vid.costumeNumber = 0 elif u == "N": vid.costumeTime = 0 try: vid.costumeNumber = int(input("Go to frame? ")) except: pass elif ev.key in (K_LEFT, K_RIGHT): vid.costumeTime = 0 n = vid.costumeNumber + (1 if ev.key == K_RIGHT else -1) if n < 0: n = len(vid) - 1 elif n >= len(vid): n = 0 vid.costumeNumber = n elif u in "[]": self.clip["[]".index(u)] = vid.costumeNumber
def __init__(self, drops=64, fill=(0, 0, 0, 0)): self.fill = rgba(fill) self.side = drops > 0 self.drops = [self.makeDrop() for i in range(abs(drops))] n = sum([d[0] for d in self.drops]) for d in self.drops: d[0] /= n
class Shape(Graphic): _fill = None _stroke = rgba((0, 0, 0)) weight = 1 @property def stroke(self): return self._stroke @stroke.setter def stroke(self, s): self._stroke = rgba(s) if s else None @property def fill(self): return self._fill @fill.setter def fill(self, s): self._fill = rgba(s) if s else None @property def avgColor(self): f = self._fill return f if f else self._stroke def contains(self, pos): "Determine if the point is within the shape, accounting for canvas offset" cv = self.canvas cvPos = delta(pos, cv.rect.topleft) if cv else pos return self.containsPoint(cvPos)
def setup(self): # Draw star field self.bg = img = Image(self.size, "black") img = img.image for i in range(150): img.set_at(randPixel(self.size), rgba(False)) # Load high scores try: with open(JSON) as f: self.highScores = json.load(f) except: self.highScores = [] # Load images Missile.original = Image(self.imgFldr + "/missile.png") Asteroid.original = Image(self.imgFldr + "/target.png") self.player = Ship(self.imgFldr + "/ship.png").config(wrap=BOTH) # Start the game self.playerName = None self.score = Score() self.collisions = Collisions(self) self.start()
def pixel(self, n, x, y, c): "Calculate pixel color" if random() <= n or (self.keep and c & self.alphaMask == 0): return c c = self.colors if type(c) is bool: return rgba(c) self.n = (self.n + 1) % len(self.colors) return c[self.n]
def circles(sk, n=50): "Draw random circles for arena floor" size = sk.size cv = Canvas(size, "white") for i in range(n): r = randint(10, size[0]/8) cv += Circle(r).config(weight=0, fill=rgba(True), pos=randPixel(size)) return cv.snapshot()
def pixel(self, n, x, y, c): "Calculate pixel color" if random() <= n or (self.keep and c & self.alphaMask == 0): return c c = self.colors if type(c) is bool: return rgba(c) self.n = (self.n + 1) % len(self.colors) return c[self.n]
def config(self, **kwargs): keys = ("bg", "color", "border", "promptColor", "data", "font", "fontSize", "fontStyle", "height", "width", "align", "padding", "spacing", "weight") if hasAny(kwargs, keys): self.stale = True for a in kwargs: v = kwargs[a] if v and a in keys[:4]: v = rgba(v) setattr(self, a, v) return self
def axis(self, n=None, ends=None, stroke="black", weight=2): "Configure the x- and/or y-axis" if n is None: for n in range(2): self.axis(n, ends, stroke, weight) else: attr = ["_xaxis", "_yaxis"][n] if not ends: ends = self._coords[2:] if n else self._coords[:2] setattr(self, attr, (ends, rgba(stroke), weight)) self.stale = True return self
def config(self, **kwargs): keys = ("data", "color", "bg", "font", "fontSize", "fontStyle", "height", "width", "align", "padding", "spacing", "weight", "border", "promptColor") if hasAny(kwargs, keys): self.stale = True for a in kwargs: v = kwargs[a] if v and a in ("bg", "color", "border", "promptColor"): v = rgba(v) setattr(self, a, v) return self
def axis(self, n=None, ends=None, stroke="black", weight=2): "Configure the x- and/or y-axis" if n is None: for n in range(2): self.axis(n, ends, stroke, weight) else: attr = ["_xaxis", "_yaxis"][n] if not ends: ends = self._coords[2:] if n else self._coords[:2] setattr(self, attr, (ends, rgba(stroke), weight)) self.stale = True return self
def __init__(self, colors=None): img = Image.fromBytes(sc8prData("robot")) if colors: # Replace body and nose colors px = pygame.PixelArray(img.image) body0, nose0, body, nose = rgba("red", "blue", *colors) orig = body0, nose0 if body in orig or nose in orig: colors = body0, nose0, body, nose body0 = _tempColor(px, body0, *colors) nose0 = _tempColor(px, nose0, *colors) if nose != nose0: px.replace(nose0, nose) if body != body0: px.replace(body0, body) img = img.tiles(2) super().__init__(img)
def __init__(self, colors=None): img = Image.fromBytes(sc8prData("robot")) if colors: # Replace body and nose colors px = pygame.PixelArray(img.image) body0, nose0, body, nose = rgba("red", "blue", *colors) orig = body0, nose0 if body in orig or nose in orig: colors = body0, nose0, body, nose body0 = _tempColor(px, body0, *colors) nose0 = _tempColor(px, nose0, *colors) if nose != nose0: px.replace(nose0, nose) if body != body0: px.replace(body0, body) img = img.tiles(2) super().__init__(img)
def options(self, options): "Convert options to a list of colors or images" if options is None: options = OPTIONS else: t = type(options) if t is dict: options = [options.get(self.statusName(i)) for i in range(5)] elif t is int: options = [OPTIONS[i] for i in range(options)] elif t is not list: options = list(options) while len(options) < 5: options.append(None) c = lambda x: rgba(x) if type(x) in (str,list,tuple) else x options = [c(i) for i in options] if options[1] is None: options[1] = options[0] if options[3] is None: options[3] = options[2] self._options = options
def _drawGrid(self, srf, n, cfg): "Draw gridlines" s = rgba(cfg["stroke"]) w = cfg["weight"] x, x1 = cfg["yends" if n else "xends"] y0, y1 = cfg["xends" if n else "yends"] dx = cfg["interval"] while x <= x1: if n: p0 = [y0, x] p1 = [y1, x] else: p0 = [x, y0] p1 = [x, y1] pygame.draw.line(srf, s, self.pixelCoords(p0), self.pixelCoords(p1), w) x += dx
def _drawGrid(self, srf, n, cfg): "Draw gridlines" s = rgba(cfg["stroke"]) w = cfg["weight"] x, x1 = cfg["yends" if n else "xends"] y0, y1 = cfg["xends" if n else "yends"] dx = cfg["interval"] while x <= x1: if n: p0 = [y0, x] p1 = [y1, x] else: p0 = [x, y0] p1 = [x, y1] pygame.draw.line(srf, s, self.pixelCoords(p0), self.pixelCoords(p1), w) x += dx
def _checkFront(self): "Update the front color sensor" # Sensor info sw = self.sensorWidth res = 0.5 * self.sensorResolution pos = delta(self.pos, vec2d(-self.radius, self.angle)) # Distance from sensor to edge of sketch obj = prox = None sk = self.sketch if sk.weight: prox = _distToWall(pos, self.angle, self.sensorWidth, *sk.size) if prox: obj = sk # Find closest object within "sensor cone" for gr in self.sensorObjects(sk): if gr is not self and gr.avgColor and hasattr(gr, "rect"): r = gr.radius view = subtend(pos, gr.rect.center, r, None if prox is None else prox + r) if view: # Object may be closer than the current closest object sep, direct, half = view if not res or half > res: # Object size meets sensor resolution threshold beta = abs(angleDifference(self.angle, direct)) - sw if beta < half or sep < r: # Object is in sensor cone pr = sep - r if beta > 0: # CLOSEST point is NOT in sensor cone dr = r + sep * (cos(half * DEG) - 1) pr += dr * (beta / half)**2 if prox is None or pr < prox: # Object is closest (so far) prox = pr obj = gr # Save data self.closestObject = obj c = rgba(sk.border if obj is sk else obj.avgColor if obj else (0, 0, 0)) self.sensorFront = noise(divAlpha(c), self.sensorNoise, 255) self.proximity = None if prox is None else max(0, round(prox))
def _checkFront(self): "Update the front color sensor" # Sensor info sw = self.sensorWidth res = 0.5 * self.sensorResolution pos = delta(self.pos, vec2d(-self.radius, self.angle)) # Distance from sensor to edge of sketch obj = prox = None sk = self.sketch if sk.weight: prox = _distToWall(pos, self.angle, self.sensorWidth, *sk.size) if prox: obj = sk # Find closest object within "sensor cone" for gr in self.sensorObjects(sk): if gr is not self and gr.avgColor and hasattr(gr, "rect"): r = gr.radius view = subtend(pos, gr.rect.center, r, None if prox is None else prox + r) if view: # Object may be closer than the current closest object sep, direct, half = view if not res or half > res: # Object size meets sensor resolution threshold beta = abs(angleDifference(self.angle, direct)) - sw if beta < half or sep < r: # Object is in sensor cone pr = sep - r if beta > 0: # CLOSEST point is NOT in sensor cone dr = r + sep * (cos(half * DEG) - 1) pr += dr * (beta / half) ** 2 if prox is None or pr < prox: # Object is closest (so far) prox = pr obj = gr # Save data self.closestObject = obj c = rgba(sk.border if obj is sk else obj.avgColor if obj else (0,0,0)) self.sensorFront = noise(divAlpha(c), self.sensorNoise, 255) self.proximity = None if prox is None else max(0, round(prox))
def draw(self, srf, transform): "Plot one data series onto a surface" # Plot stroke pts = [transform(p) for p in self.pointGen()] s, w = self.stroke, self.weight if s and w: pygame.draw.lines(srf, rgba(s), False, pts, w) # Plot markers marker = self.marker if marker: i = 0 for p in pts: if isinstance(marker, Graphic): img = marker else: img = marker[i] i += 1 if img.canvas: img.remove() img.pos = p sz = img.size if img.angle: sz = rotatedSize(*sz, img.angle) pos = img.blitPosition((0, 0), sz) srf.blit(img.image, pos)
def draw(self, srf, transform): "Plot one data series onto a surface" # Plot stroke pts = [transform(p) for p in self.pointGen()] s, w = self.stroke, self.weight if s and w: pygame.draw.lines(srf, rgba(s), False, pts, w) # Plot markers marker = self.marker if marker: i = 0 for p in pts: if isinstance(marker, Graphic): img = marker else: img = marker[i] i += 1 if img.canvas: img.remove() img.pos = p sz = img.size if img.angle: sz = rotatedSize(*sz, img.angle) pos = img.blitPosition((0,0), sz) srf.blit(img.image, pos)
# "sc8pr" is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with "sc8pr". If not, see <http://www.gnu.org/licenses/>. from sc8pr.image import Image from sc8pr.util import rgba, CENTER, EAST, NORTH, WEST, SOUTH, NE, NW from sc8pr.geom import locus, arrow, add import pygame BLACK, WHITE, GREY = rgba("black", "white", "grey") class Plot(Image): def __init__(self, img, xrange, yrange=None, margin=0): "Encapsulate an Image with an associated coordinate system" super().__init__(img) w, h = self.size xmin, xmax = xrange if type(margin) is int: margin = [margin for i in range(4)] mx1, mx2, my2, my1 = margin xscale = (w - 1 - (mx1 + mx2)) / (xmax - xmin) if yrange is None or type(yrange) in (int, float): yscale = -xscale
def __init__(self, drops=64, fill=(0,0,0,0)): self.fill = rgba(fill) self.side = drops > 0 self.drops = [self.makeDrop() for i in range(abs(drops))] n = sum([d[0] for d in self.drops]) for d in self.drops: d[0] /= n
def fill(self, s): self._fill = rgba(s) if s else None @property
def stroke(self, s): self._stroke = rgba(s) if s else None @property
def __init__(self, color=(255,255,255,0)): self.color = rgba(color)
class Text(Renderable): font = None fontSize = 24 fontStyle = 0 bg = None color = border = rgba("black") weight = 0 padding = 0 spacing = 0 align = LEFT def __init__(self, data=""): self.data = data def __iadd__(self, v): self.data += v self.stale = True return self def config(self, **kwargs): keys = ("bg", "color", "border", "promptColor", "data", "font", "fontSize", "fontStyle", "height", "width", "align", "padding", "spacing", "weight") if hasAny(kwargs, keys): self.stale = True for a in kwargs: v = kwargs[a] if v and a in keys[:4]: v = rgba(v) setattr(self, a, v) return self def render(self): "Render the text as an Image" font = Font.get(self.font, self.fontSize, self.fontStyle) text = str(self.data).split("\n") srfs = [font.render(t, True, self.color) for t in text] return self._joinLines(srfs) def _joinLines(self, srfs): "Join the lines of text into a single surface" # Calculate total size wMax, hMax = 0, 0 n = len(srfs) for s in srfs: w, h = s.get_size() if w > wMax: wMax = w if h > hMax: hMax = h y = self.padding w = wMax + 2 * y h = n * hMax + (n - 1) * self.spacing + 2 * y # Blit lines to final image wt = self.weight dx = 2 * wt y += wt srf = Image((w + dx, h + dx), self.bg).image a = self.color.a for s in srfs: if a < 255: setAlpha(s, a) x = self.padding + wt if self.align != LEFT: dx = wMax - s.get_size()[0] x += dx if self.align == RIGHT else dx // 2 srf.blit(s, (x, y)) y += hMax + self.spacing if wt: drawBorder(srf, self.border, wt) return srf def resize(self, size): self.fontSize *= size[1] / self.height self.stale = True
# # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. "A demonstration of some of sc8pr 2.0's GUI controls" from sc8pr import Sketch, Canvas, Image, TOPLEFT, TOPRIGHT, CENTER, TOP from sc8pr.util import rgba, nothing, sc8prData from sc8pr.text import BOLD, Font from sc8pr.gui.textinput import TextInput from sc8pr.gui.radio import Radio, Options from sc8pr.gui.slider import Slider from sc8pr.gui.button import Button from sc8pr.gui.menu import Menu, R_TRIANGLE GREY, BLUE = rgba("#ececec", "blue") FONT = Font.sans() def buttons(cfg): "Create a canvas containing two buttons" cv = Canvas((256, 48)) # Selectable button btn1 = Button((120, 48)).textIcon("Selectable\nButton", True) cv["Selectable"] = btn1.config(anchor=TOPLEFT).bind(onaction=buttonClick) # Non-selectable button btn2 = Button(btn1.size, 2).textIcon("Popup Menu").bind(onaction=buttonClick) cv["Popup"] = btn2.config(pos=(255, 0), anchor=TOPRIGHT) # Configure button text
def pen(self, p): if p and len(p) == 2: p = p + (self._pen[2] if self._pen else None, ) if p: p = (rgba(p[0]), ) + p[1:] self._pen = p
def stroke(self, s): self._stroke = rgba(s) if s else None @property
def fill(self, s): self._fill = rgba(s) if s else None @property
def __init__(self, slope=False, above=True, fill=(0,0,0,0)): self.slope = slope self.above = above self.fill = rgba(fill)
def __init__(self, clockwise=True, fill=(0, 0, 0, 0)): self.fill = rgba(fill) self.cw = clockwise
def __init__(self, color, replace=(0,0,0,0), dist=0.0): self.color1 = rgba(color) self.color2 = rgba(replace) self.dist = dist
class TextInput(Text): """Editable text GUI control: handles ondraw, onclick, ondrag, onkeydown, onblur; triggers onchange, onaction""" focusable = True cursorTime = 1.0 cursorOn = 0.35 promptColor = rgba("#d0d0d0") padding = 4 allowButton = 1, blurAction = True _cursorX = 0 _scrollX = 0 _selection = None _highlight = None _submit = False def __init__(self, data="", prompt=None): super().__init__(str(data).split("\n")[0]) self.cursor = len(self.data) self.cursorStatus = False self.prompt = prompt @property def highlight(self): if self._highlight: return self._highlight c = self.color return pygame.Color(c.r, c.g, c.b, 48) @highlight.setter def highlight(self, c): self._highlight = rgba(c) def _startCursor(self): self.stale = True self.cursorStatus = True self.cursorStart = self.sketch.frameCount def draw(self, srf): if self.focussed: sk = self.sketch try: n = (sk.frameCount - self.cursorStart) / sk.frameRate except: n = None if n is None or n > self.cursorTime: self._startCursor() else: c = n < self.cursorOn if c is not self.cursorStatus: self.cursorStatus = c self.stale = True elif self.cursorStatus: self.cursorStatus = False self.stale = True return super().draw(srf) def render(self): "Render the text as an Image" font = Font.get(self.font, self.fontSize, self.fontStyle) try: focus = self is self.sketch.evMgr.focus except: focus = False prompt = self.prompt and not self.data and not focus if prompt: color = self.promptColor text = self.prompt else: color = self.color text = self.data try: srf = font.render(text, True, color) except: text = "[Unable to render!]" srf = font.render(text, True, color) if prompt: self.prompt = text else: self.data = text self.cursor = 0 srf = style(srf, self.bg, self.border, self.weight, self.padding) # Highlight selection and draw cursor c = self.cursor if self.data: p0 = text[c-1:c] if c else text[0] p1 = text[c:c+1] if c < len(self.data) else text[-1] self._scrollPad = [font.size(p)[0] for p in (p0, p1)] else: self._scrollPad = 0, 0 x = font.size(text[:c])[0] p = self.padding x += p self._cursorX = x h = srf.get_height() s = self._selection if s not in (None, self.cursor): x0 = font.size(text[:s])[0] + p w = abs(x - x0) s = pygame.Surface((w, h - 2 * p), pygame.SRCALPHA) s.fill(self.highlight) srf.blit(s, (min(x, x0), p)) if self.cursorStatus: pygame.draw.line(srf, self.color, (x,p), (x,h-1-p), 2) return srf def _paste(self, data): "Paste the clipboard contents at the cursor location" clip = clipboardGet() if clip: for c in "\n\r\t": clip = clip.replace(c, "") c = self.cursor data = data[:c] + clip + data[c:] self.config(data=data) self.cursor += len(clip) return True def _selectRange(self): c = self.cursor s = self._selection if s is None: s = c else: tmp = max(s, c) c = min(s, c) s = tmp return c, s def deleteSelection(self): c, s = self._selectRange() if c == s: return False self.stale = True self.data = self.data[:c] + self.data[s:] self._selection = None self.cursor = c return True def onkeydown(self, ev): # Process 'submit' action u = ev.unicode if u in ("\n", "\r"): #, "\t"): self._submit = True self.blur(True) self._submit = False return # Ignore keys with Ctrl or Alt modifier (except Ctrl+[ACVX]) k = ev.key acvx = [ord(c) for c in "acvxACVX"] if (ev.mod & KMOD_ALT or (ev.mod & KMOD_CTRL and k not in acvx)): return # Mark rendering as stale self.stale = True self._startCursor() cursor = self.cursor data = self.data n = len(data) # Process Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X if ev.mod & KMOD_CTRL: acvx = acvx.index(k) % 4 if acvx % 2: c, s = self._selectRange() if c != s: clipboardPut(data[c:s]) if acvx == 0: self._selection = 0 self.cursor = n elif acvx > 1: change = self.deleteSelection() if acvx == 2: if self._paste(self.data): change = True if change: self.bubble("onchange", ev) return # Arrow keys if k in (K_LEFT, K_RIGHT, K_HOME, K_END): if ev.mod & KMOD_SHIFT: if self._selection is None: self._selection = cursor else: self._selection = None if cursor: if k == K_LEFT: cursor -= 1 elif k == K_HOME: cursor = 0 if cursor < n: if k == K_RIGHT: cursor += 1 elif k == K_END: cursor = n self.cursor = cursor return # Backspace and delete if k in (K_BACKSPACE, K_DELETE): if self.deleteSelection(): cursor = self.cursor elif cursor and k == K_BACKSPACE: self.data = data[:cursor-1] + data[cursor:] cursor -= 1 elif cursor < n and k == K_DELETE: self.data = data[:cursor] + data[cursor+1:] # Character keys if k >= 32 and k < 127: if self.deleteSelection(): cursor = self.cursor data = self.data self.data = data[:cursor] + u + data[cursor:] cursor += 1 # Finish up self.cursor = cursor if self.data != data: self._selection = None self.bubble("onchange", ev) def _widthTo(self, i): font = Font.get(self.font, self.fontSize, self.fontStyle) d = self.data return (font.size(d[:i])[0] + font.size(d[:i+1])[0]) // 2 def onclick(self, ev): drag = ev.handler == "ondrag" if drag or ev.button in self.allowButton: self._startCursor() x = self.relXY(ev.pos)[0] - self.padding n = len(self.data) i = 0 while i < n and x > self._widthTo(i): i += 1 k = self.sketch.key if drag or k and k.type == KEYDOWN and k.key in (K_LSHIFT, K_RSHIFT): if self._selection is None: self._selection = self.cursor else: self._selection = None self.cursor = i self.stale = True elif not hasattr(self, "cursorStart"): self.blur() ondrag = onclick def onblur(self, ev): self._selection = None if not self.data: self.stale = True if hasattr(self, "cursorStart"): del self.cursorStart cv = self.canvas if (self._submit or self.blurAction) and not (ev.focus is cv and isinstance(cv, TextInputCanvas) and cv.ti is self): self.bubble("onaction", ev) def _scrollCalc(self, a): """Calculate how many pixels to scroll to keep the text insertion point visible within the canvas""" if a not in (0, 90): raise ValueError(_ANGLE_ERROR) if a: a = 1 cv = self.canvas width = cv.size[a] if self.width < width: return 0, False pad = self.padding x = self.rect.topleft[a] + self._cursorX - cv.rect.topleft[a] p0, p1 = self._scrollPad c = self.cursor if c == 0: p0 = 0 if c == len(self.data): p1 = 0 if x < pad + p0: pix = pad + p0 - x else: pad += p1 width -= pad + 1 if x > width: pix = width - x else: pix = 0 return pix, True def scroll(self, pix=None, rel=True): # Calculate scroll when not specified a = self.angle if pix is None: pix = self.focussed and a in (0, 90) if pix is False: pix, rel = 0, False elif pix is True: pix, rel = self._scrollCalc(a) # Set scrolling attributes if pix or not rel: if rel: self._scrollX += pix else: tmp = pix pix -= self._scrollX self._scrollX = tmp if pix: if a == 90: self.pos = self.pos[0], self.pos[1] + pix else: self.pos = sigma(self.pos, vec2d(pix, a)) return self ondraw = scroll
class Canvas(Graphic): _border = rgba("black") _scroll = 0, 0 clipArea = None weight = 0 resizeContent = True iconSize = 32, 32 @staticmethod def _px(x): return x _cs = _px def __init__(self, image, bg=None): mode = 0 if type(image) is str else 1 if isinstance(image, Image) else 2 if mode == 2: # tuple or list size = image elif bg: bg = Image(image, bg) size = bg.size else: # str or Image bg = image if mode else Image(image) size = bg.size self._size = size self._items = [] self.icons = [] # !!! self.bg = bg @property def clockwise(self): return True @property def units(self): return 1, 1 @property def unit(self): return 1 def px(self, *pt): return delta(self._px(pt), self._scroll) def cs(self, *pt): return self._cs(sigma(pt, self._scroll)) def call(self, methodname, seq, *args, **kwargs): "Call the specified method on the canvas contents" if type(seq) is bool: seq = self.everything() if seq else self for obj in seq: fn = getattr(obj, methodname, None) if fn: fn(*args, **kwargs) @property def border(self): return self._border @border.setter def border(self, color): self._border = rgba(color) @property def clipRect(self): "Calculate the clipping rect so as not to draw outside of the canvas" cv = self.canvas r = self.rect if self.clipArea: r = r.clip(self.clipArea.move(*r.topleft)) return r.clip(cv.clipRect) if cv else r @property def angle(self): return 0 @property def bg(self): return self._bg @bg.setter def bg(self, bg): self._setBg(bg) def _setBg(self, bg): t = type(bg) if t is str: bg = pygame.Color(bg) elif t in (tuple, list): bg = pygame.Color(*bg) self._bg = bg @property def avgColor(self): bg = self.bg return bg.avgColor if isinstance(bg, Image) else bg def _paint(self, p1, p2, c=(255, 0, 0), w=4): try: cs = self.bg._srf except AttributeError: msg = "painting on canvas requires a background image" raise AttributeError(msg) key, scaled = cs.scaled if scaled is cs.original: scaled = scaled.copy() cs.scaled = key, scaled pygame.draw.line(scaled, c, p1, p2, w) def __len__(self): return len(self._items) def __contains__(self, i): for gr in self._items: if gr is i or getattr(gr, "_name", None) == i: return True return False def __getitem__(self, i): if type(i) in (int, slice): return self._items[i] if i: for gr in self._items: if getattr(gr, "_name", None) == i: return gr raise KeyError("{} contains no items with key '{}'".format(self, i)) def __setitem__(self, key, gr): t = type(key) if not key.__hash__: raise KeyError("Type '{}' cannot be used as a key".format( t.__name__)) elif t is slice: raise KeyError("Assignment by layer is not supported") if gr not in self: if t is int: n = key key = None gr.setCanvas(self, key) if t is int: gr.config(layer=n) def __iadd__(self, gr): "Add a Graphics instance(s) to the Canvas" if isinstance(gr, Graphic): gr.setCanvas(self) else: for g in gr: g.setCanvas(self) return self def __isub__(self, gr): "Remove item(s) from the canvas" if isinstance(gr, Graphic): if gr in self: gr.remove() else: raise ValueError("Cannot remove {} from {}".format(gr, self)) else: for g in gr: self -= g return self append = __iadd__ def purge(self, recursive=False): "Remove all content" while len(self._items): gr = self[-1] if recursive and isinstance(gr, Canvas): gr.purge() gr.remove() def shiftContents(self, offset, *args, resize=True): "Move contents and (optionally) adjust canvas size" dx, dy = offset for gr in self: if gr not in args: x, y = gr.pos gr.pos = x + dx, y + dy if resize: self._size = self._size[0] + dx, self._size[1] + dy return self def resize(self, size, resizeContent=None): "Resize the canvas contents" size = max(1, round(size[0])), max(1, round(size[1])) fx, fy = size[0] / self._size[0], size[1] / self._size[1] self._size = size # Resize content if resizeContent is None: resizeContent = self.resizeContent if resizeContent: for g in self: if g.autoPositionOnResize: g.scaleVectors(fx, fy) w, h = g.size g.resize((w * fx, h * fy)) def draw(self, srf=None, mode=3): "Draw the canvas to a surface" # Calculate blit rectangle if srf is None: srf = self.image self.rect = r = self.calcBlitRect(self.size) # Draw background isSketch = isinstance(self, Sketch) if mode & 1: srf.set_clip(self.clipRect) if isinstance(self._bg, Image): self._bg.config(size=self._size) if isSketch and self.dirtyRegions: self._drawDirtyRegions(srf) else: srf.blit(self._bg.image, r.topleft) elif self._bg: srf.fill(self._bg) # Draw objects if mode & 2: br = isSketch and self.dirtyRegions is not None if br: self.dirtyRegions = [] ondrawList = self.sketch.ondrawList for g in list(self): srf.set_clip(self.clipRect) if not hasattr(g, "image") and g.effects: img = g.snapshot() for a in ["pos", "anchor", "canvas", "effects"]: setattr(img, a, getattr(g, a)) grect = img.draw(srf) else: grect = g.draw(srf) g.rect = grect if br: self.dirtyRegions.append(grect) if g.ondraw: ondrawList.append(g) # g.ondraw() # Draw border if mode & 1 and self.weight: drawBorder(srf, self.border, self.weight, r) srf.set_clip(None) return r def snapshot(self): "Capture the canvas as an Image instance" srf = pygame.Surface(self.size, pygame.SRCALPHA) # Draw background if isinstance(self._bg, Image): self._bg.config(size=self._size) srf.blit(self._bg.image, (0, 0)) elif self._bg: srf.fill(self._bg) # Draw objects for g in self: if g.snapshot is not None: img = g.snapshot().image srf.blit(img, g.blitPosition((0, 0), img.get_size())) # xy = g.blitPosition((0,0), g.size) # srf.blit(g.snapshot().image, xy) else: g.draw(srf, snapshot=True) # Draw border if self.weight: drawBorder(srf, self.border, self.weight) return Image(srf) def flatten(self, keep=()): "Draw graphics onto background and remove" if isinstance(keep, Graphic): keep = (keep, ) keep = [(gr.name, gr) for gr in keep] for gr in keep: gr[1].remove() self.config(bg=self.snapshot()).purge() for name, gr in keep: if name: self[name] = gr else: self += gr return self def objectAt(self, pos, includeAll=False): obj = self for g in self: try: # Objects added but not yet blitted have no rect if (includeAll or g.hoverable) and g.contains(pos): obj = g.objectAt(pos) if isinstance(g, Canvas) else g except: pass return obj def instOf(self, cls): "Yield all instance of the specified Graphics class" for g in self: if isinstance(g, cls): yield g def sprites(self): return self.instOf(BaseSprite) def everything(self): "Iterate through all Graphics recursively" for gr in self: yield gr if isinstance(gr, Canvas): for i in gr.everything(): yield i def find(self, criteria, recursive=False): "Yield all Graphics that meet the criteria" for gr in (self.everything() if recursive else self): if criteria(gr): yield gr def scroll(self, dx=0, dy=0): raise NotImplementedError("Use PCanvas class to scroll.") def cover(self): return Image(self.size, "#ffffffc0").config(anchor=TOPLEFT)
def highlight(self, c): self._highlight = rgba(c) def _startCursor(self):
def __init__(self, noise=0.15, fill=(0, 0, 0, 0)): #, eqn=None): self.fill = rgba(fill) self.above = noise > 0 self.noise = abs(noise)
def stroke(self, s): self._stroke = rgba(s) if s else None def __init__(self, x, y=None, param=None):
from sc8pr import Sketch, Canvas, Image, TOPLEFT, TOPRIGHT, CENTER, TOP from sc8pr.util import rgba, nothing, sc8prData from sc8pr.text import BOLD, Font from sc8pr.gui.textinput import TextInput from sc8pr.gui.radio import Radio, Options from sc8pr.gui.slider import Slider from sc8pr.gui.button import Button from sc8pr.gui.menu import Menu, R_TRIANGLE try: # v2.2 from sc8pr.gui.textinput import TextInputCanvas except: # v2.0-2.1 TextInputCanvas = None GREY, BLUE = rgba("#ececec", "blue") FONT = Font.sans() def setup(sk): # Create a Canvas as a GUI dialog cv = Canvas((384, 256)).config(bg="#f0f0ff", weight=1) # Vertical positioning 16 pixels below last item added down = lambda cv: 16 + cv[-1].height text = dict(color=BLUE, font=FONT, fontStyle=BOLD, padding=4) if TextInputCanvas: # v2.2.dev ti = TextInputCanvas(336, "", "Type Some Text...", **text) else: # v2.0-2.1 ti = TextInput("", "Type Some Text...").config(**text)
def border(self, color): self._border = rgba(color)
def _tempColor(px, color, *args): "Replace one color with a random RGB color" c = color while c in args: c = rgba(False) if c != color: px.replace(color, c) return c
def __init__(self, color=(255, 255, 255, 0)): self.color = rgba(color)
def __init__(self, clockwise=True, fill=(0,0,0,0)): self.fill = rgba(fill) self.cw = clockwise
def __init__(self, slope=False, above=True, fill=(0, 0, 0, 0)): self.slope = slope self.above = above self.fill = rgba(fill)
class Series: "Represents a single data series within the plot" _stroke = rgba((0, 0, 0)) weight = 0 marker = None @property def stroke(self): return self._stroke @stroke.setter def stroke(self, s): self._stroke = rgba(s) if s else None def __init__(self, x, y=None, param=None): self._data = x if y is None else list(zip(x, y)) self.param = param self.vars = {} def __getitem__(self, i): return self._data[i] def __setitem__(self, i, pt): self._data[i] = pt config = Graphic.config def pointGen(self): "Iterable sequence of points" data = self._data return data if type(data) in (list, tuple) else locus( data, self.param, **self.vars) @property def pointList(self): "Return data as a new list" return list(self.pointGen()) def dataGen(self, n): "Generate values from x or y column of data table" for pt in self.pointGen(): yield pt[n] @property def x(self): return list(self.dataGen(0)) @property def y(self): return list(self.dataGen(1)) def regression(self, model=leastSq): return model(*[list(self.dataGen(i)) for i in (0, 1)]) def transform(self, **kwargs): "Apply a transformation to the data points" try: self._data = list(transform2dGen(self._data, **kwargs)) except: raise TypeError( "Series.transform cannot be applied to an equation") return self def draw(self, srf, transform): "Plot one data series onto a surface" # Plot stroke pts = [transform(p) for p in self.pointGen()] s, w = self.stroke, self.weight if s and w: pygame.draw.lines(srf, rgba(s), False, pts, w) # Plot markers marker = self.marker if marker: i = 0 for p in pts: if isinstance(marker, Graphic): img = marker else: img = marker[i] i += 1 if img.canvas: img.remove() img.pos = p sz = img.size if img.angle: sz = rotatedSize(*sz, img.angle) pos = img.blitPosition((0, 0), sz) srf.blit(img.image, pos) @staticmethod def _lattice(x=0, y=0): "Generate a lattice of points" num = int, float x = (x, ) if type(x) in num else rangef(*x) y = (y, ) if type(y) in num else list(rangef(*y)) for i in x: for j in y: yield i, j @staticmethod def _tick(param, marker=9, y=False, **kwargs): if type(marker) is int: marker = ((9, 1) if y else (1, 9), "black") label = type(marker) is str if not (label or isinstance(marker, Graphic)): marker = Image(*marker) s = list(Series._lattice(0, param) if y else Series._lattice(param, 0)) if label: isZero = (lambda x: _isZero(x, marker)) if kwargs.get("omitZero")\ else (lambda x: False) i = 1 if y else 0 text = list(marker.format(x[i]) for x in s) marker = [ Text("" if isZero(x) else x).config(**kwargs) for x in text ] return Series(s).config(marker=marker) def scaleMarkers(self, s): marker = self.marker if isinstance(marker, Graphic): marker = [marker] if marker: for gr in marker: gr.height *= s
def pen(self, p): if p and len(p) == 2: p = p + (self._pen[2] if self._pen else None,) if p: p = (rgba(p[0]),) + p[1:] self._pen = p
def border(self, color): self._border = rgba(color) @property
def __init__(self, color, replace=(0, 0, 0, 0), dist=0.0): self.color1 = rgba(color) self.color2 = rgba(replace) self.dist = dist
def __init__(self, noise=0.15, fill=(0,0,0,0)):#, eqn=None): self.fill = rgba(fill) self.above = noise > 0 self.noise = abs(noise)