Source code for app

"""
App
- there is only one App object
- an app has multiple scenes (App.scenes)
- an app has one current scene (App.scene)
- an app has one window to draw in (App.screen)

Scene

- a scene has multiple nodes (App.scene.nodes)
- nodes are ordered: the last in the list is displayed last
- the node which is clicked becomes active
- the active node becomes the top node
- the active node has focus (App.scene.focus)
- TAB and shift-TAB select the next node

Node (object)

- nodes have default position and size (pos, size)
- nodes are automatically placed at creation (dir, gap)
- nodes inherit options (color, size, ...) from the previous object

A Node object has the following properties

- clickable: mouse-click has effect
- movable: can be moved (mouse, arrow-keys)
- visible: is drawn
- has focus

Debug

- print events to console (cmd+E)
- display node label (cmd+L)
- display outline (cmd+O)
"""

import copy
import inspect
import os
import sys

import numpy as np
import pygame
from pygame.locals import *

DBG_EVENTS = 1
DBG_LABELS = 2
DBG_OUTLINE = 4

DBL_CLICK_TIMER = pygame.USEREVENT
DBL_CLICK_TIMEOUT = 200

[docs]class App: """Create a single-window app with multiple scenes having multiple objects.""" scenes = [] # scene list scene = None # current scene screen = None # main display window running = True # the app is running focus = None # current object for cut/copy/paste selection = None # list of selected objects (cut/copy/paste) root = None debug = DBG_LABELS + DBG_OUTLINE key_repeat = 200, 100 def __init__(self, size=(640, 240), shortcuts={}): """Initialize pygame and the application.""" pygame.init() pygame.key.set_repeat(*App.key_repeat) self.flags = 0 # RESIZABLE, FULLSCREEN, NOFRAME self.rect = Rect(0, 0, *size) App.screen = pygame.display.set_mode(self.rect.size, self.flags) App.root = self self.shortcuts = { (K_ESCAPE, KMOD_NONE): 'App.running=False', (K_q, KMOD_LMETA): 'App.running=False', (K_f, KMOD_LMETA): 'self.toggle_fullscreen()', (K_r, KMOD_LMETA): 'self.toggle_resizable()', (K_g, KMOD_LMETA): 'self.toggle_frame()', (K_h, KMOD_LMETA): 'pygame.display.iconify()', (K_p, KMOD_LMETA): 'self.capture()', (K_s, KMOD_LMETA): 'self.next_scene()', (K_s, KMOD_LMETA+KMOD_LSHIFT): 'self.next_scene(-1)', (K_TAB, KMOD_NONE): 'App.scene.next_focus()', (K_TAB, KMOD_LSHIFT): 'App.scene.next_focus(-1)', (K_e, KMOD_LCTRL): 'Ellipse(pos=pygame.mouse.get_pos(), size=(100, 60))', (K_i, KMOD_LCTRL): 'Node(pos=pygame.mouse.get_pos(), size=(100, 60))', (K_n, KMOD_LCTRL): 'Node(pos=pygame.mouse.get_pos(), size=(100, 60))', (K_r, KMOD_LCTRL): 'Rectangle(pos=pygame.mouse.get_pos(), size=(100, 60))', (K_t, KMOD_LCTRL): 'Text(pos=pygame.mouse.get_pos())', (K_x, KMOD_LMETA): 'print("cmd+X")', (K_x, KMOD_LALT): 'print("alt+X")', (K_x, KMOD_LCTRL): 'print("ctrl+X")', (K_x, KMOD_LMETA + KMOD_LSHIFT): 'print("cmd+shift+X")', (K_x, KMOD_LMETA + KMOD_LALT): 'print("cmd+alt+X")', (K_x, KMOD_LMETA + KMOD_LALT + KMOD_LSHIFT): 'print("cmd+alt+shift+X")', (K_x, KMOD_LMETA): 'App.scene.cut()', (K_c, KMOD_LMETA): 'App.scene.copy()', (K_v, KMOD_LMETA): 'App.scene.paste()', (K_e, KMOD_LMETA): 'App.debug ^= DBG_EVENTS', (K_l, KMOD_LMETA): 'App.debug ^= DBG_LABELS', (K_o, KMOD_LMETA): 'App.debug ^= DBG_OUTLINE', (K_d, KMOD_LMETA): 'App.scene.debug()', } # update shortcuts with the argument self.shortcuts.update(shortcuts)
[docs] def run(self): """Run the main event loop.""" while App.running: for event in pygame.event.get(): if event.type == QUIT: App.running = False elif event.type == KEYDOWN: self.do_shortcut(event) # Send the event to the scene App.scene.do_event(event) App.scene.update() App.scene.draw() pygame.quit()
[docs] def next_scene(self, d=1): """Switch to the next scene.""" i = App.scenes.index(App.scene) n = len(App.scenes) i = (i+d) % n App.scene = App.scenes[i] App.scene.enter()
[docs] def do_shortcut(self, event): """Find the key/mod combination in the dictionary and execute the cmd.""" k = event.key m = event.mod if (k, m) in self.shortcuts: exec(self.shortcuts[k, m])
[docs] def capture(self): """Save a screen capture to the directory of the calling class, under the class name in PNG format.""" name = type(self).__name__ module = sys.modules['__main__'] path, name = os.path.split(module.__file__) name, ext = os.path.splitext(name) filename = path + '/' + name + '.png' pygame.image.save(App.screen, filename)
[docs] def toggle_fullscreen(self): """Toggle between full screen and windowed screen.""" self.flags ^= FULLSCREEN pygame.display.set_mode((0, 0), self.flags)
[docs] def toggle_resizable(self): """Toggle between resizable and fixed-size window.""" self.flags ^= RESIZABLE pygame.display.set_mode(self.rect.size, self.flags)
[docs] def toggle_frame(self): """Toggle between frame and noframe window.""" self.flags ^= NOFRAME pygame.display.set_mode(self.rect.size, self.flags)
def __str__(self): return self.__class__.__name__
[docs]class Scene: """Create a new scene and initialize the node options.""" options = { 'id': 0, 'bg': Color('gray'), # background color #'caption': 'Pygame', # window caption 'img_folder': '', # image folder 'snd_folder': '', # sound folder 'file': '', # background image file 'focus': None, # currently active node 'status': '', 'shortcuts': {}, 'selecting': False, 'selection': [], 'selection_rect': Rect(0, 0, 0, 0), 'selection_surround': False, 'moving': False, } selection_border = (Color('cyan'), 1) status_line = (Color('black'), Color('gray'), 20) # col, bg, size def __init__(self, caption='Pygame', remember=True, **options): # Append the new scene and make it the current scene App.scenes.append(self) App.scene = self self.nodes = [] self.caption = caption self.clicks = 0 # for double-clicks self.text = '' # for copy/paste # Reset Node options to default Node.reset_options() # update existing Scene options, without adding new ones if remember: for k in options: if k in Scene.options: Scene.options[k] = options[k] else: raise TypeError(f"'{k}' is an invalid keyword argument for Scene()") # Add/update instance options from class options self.__dict__.update(Scene.options) if not remember: self.__dict__.update(options) Scene.options['id'] += 1 self.rect = App.screen.get_rect() if self.file != '': self.load_img(self.file) else: self.img = pygame.Surface(self.rect.size) self.img.fill(self.bg) self.render_status() self.enter()
[docs] def load_img(self, file): """Load the background image.""" module = sys.modules['__main__'] path, name = os.path.split(module.__file__) path = os.path.join(path, self.img_folder, file) self.img = pygame.image.load(path) self.img = pygame.transform.smoothscale(self.img, self.rect.size)
[docs] def enter(self): """Enter a scene.""" pygame.display.set_caption(self.caption)
[docs] def update(self): """Update the nodes in a scene.""" for node in self.nodes: node.update()
[docs] def set_status(self, txt): """Set status text and render it.""" self.status = txt self.render_status()
[docs] def render_status(self): """Render the status text.""" col, bg, size = Scene.status_line font = pygame.font.Font(None, size) self.status_img0 = font.render(self.status, True, col, bg) w, h = font.size(self.status) self.status_rect = Rect(0, 0, self.rect.width, h) self.status_rect.bottomleft = self.rect.bottomleft self.status_img = pygame.Surface((self.rect.width, h), flags=SRCALPHA) if bg != None: self.status_img.fill(bg) self.status_img.blit(self.status_img0, (0, 0))
[docs] def draw(self): """Draw all objects in the scene.""" App.screen.blit(self.img, self.rect) for node in self.nodes: node.draw() col, d = Scene.selection_border pygame.draw.rect(App.screen, col, self.selection_rect, d) App.screen.blit(self.status_img, self.status_rect) pygame.display.flip()
[docs] def do_event(self, event): """Handle the events of the scene.""" mods = pygame.key.get_mods() if App.debug & DBG_EVENTS: print(event) self.set_status(str(event)) if event.type == KEYDOWN: k = event.key m = event.mod if (k, m) in self.shortcuts: exec(self.shortcuts[k, m]) if event.type == MOUSEBUTTONDOWN: pygame.time.set_timer(DBL_CLICK_TIMER, DBL_CLICK_TIMEOUT) self.clicks += 1 if self.selection_rect.collidepoint(event.pos): self.moving = True else: self.focus = None for node in reversed(self.nodes): if node.rect.collidepoint(event.pos): self.focus = node self.set_status(str(node)) # place node on top self.nodes.remove(node) self.nodes.append(node) break if self.focus == None: self.set_status(str(self)) self.selecting = True self.selection_rect = Rect(event.pos, (0, 0)) elif event.type == MOUSEMOTION: if self.selecting: self.selection_rect.width += event.rel[0] self.selection_rect.height += event.rel[1] if self.moving: for node in self.selection: node.rect.move_ip(event.rel) node.label_rect.move_ip(event.rel) self.selection_rect.move_ip(event.rel) elif event.type == MOUSEBUTTONUP: self.selection_rect.normalize() self.selecting = False self.moving = False self.selection = [] for node in self.nodes: if self.selection_surround: if self.selection_rect.contains(node.rect): self.selection.append(node) else: if self.selection_rect.colliderect(node.rect): self.selection.append(node) if len(self.selection) > 0: self.selection_rect = self.selection[0].rect.copy() for x in self.selection[1:]: self.selection_rect.union_ip(x.rect) self.selection_rect.inflate_ip((4, 4)) elif event.type == DBL_CLICK_TIMER: pygame.time.set_timer(DBL_CLICK_TIMER, 0) print(self.clicks, 'clicks in', self.focus) if self.focus: if self.clicks == 2: self.focus.double_click() elif self.clicks == 3: self.focus.triple_click() self.clicks = 0 if self.focus != None: self.focus.do_event(event)
[docs] def next_focus(self, d=1): """Advance focus to next node.""" if self.focus == None: self.focus = self.nodes[0] else: i = self.nodes.index(self.focus) n = len(self.nodes) i = (i+d) % n self.focus = self.nodes[i]
[docs] def cut(self): """Cuts the selected objects and places them in App.selection.""" App.selection = self.selection for x in self.selection: self.nodes.remove(x) self.selection = []
[docs] def copy(self): """Copies the selected objects and places them in App.selection.""" App.selection = self.selection
[docs] def paste(self): """Pastes the objects from App.selection.""" print('paste')
# obj = App.focus # obj2 = eval(type(obj).__name__+'()') # obj2.rect = obj.rect.copy() # #®obj2.__dict__.update(obj.__dict__) # obj2.rect.topleft = pygame.mouse.get_pos() # obj2.label_rect.bottomleft = pygame.mouse.get_pos() # self.nodes.append(obj2)
[docs] def debug(self): """Print all scene/node options.""" obj = self.focus if self.focus else self print('===', obj, '===') for k, v in obj.__dict__.items(): print(k, '=', v)
def __str__(self): return f'Scene{self.id}'
[docs]class Node: """Create a node object with automatic position and inherited size.""" # initial options for nodes in a new scene options0 = {'pos': (20, 20), 'size': (100, 40), 'dir': (0, 1), 'gap': (10, 10), 'id': 0, 'keep': True, 'file': '', 'bg': None, 'img': None, 'cmd': '', # command string 'visible': True, 'movable': True, 'resizable': True, } # current options dictionary for each node options = options0.copy() resizing = False moving = False # color and size/thickness label = Color('red'), 14 outline = Color('red'), 1 focus = Color('blue'), 1 selection = Color('green'), 2 # key direction vectors dirs = {K_LEFT:(-1, 0), K_RIGHT:(1, 0), K_UP:(0, -1), K_DOWN:(0, 1)} @classmethod def reset_options(cls): cls.options = cls.options0.copy() @staticmethod def increment_id(): Node.options['id'] += 1 def __init__(self, **options): # update existing Node options, without adding new ones self.set_options(Node, options) self.increment_id() self.calculate_pos(options) self.rect = Rect(*self.pos, *self.size) App.scene.nodes.append(self) self.render_label() self.create_img() self.color_img() if self.file != '': self.load_img()
[docs] def set_options(self, cls, options): """Set instance options from class options.""" if 'keep' in options: Node.options['keep'] = options['keep'] if Node.options['keep']: # update class options from instance option for key in options: if key in cls.options: cls.options[key] = options[key] self.__dict__.update(cls.options) self.__dict__.update(options)
[docs] def create_img(self): """Create the image surface, and the original img0.""" self.img = pygame.Surface(self.rect.size, flags=SRCALPHA) self.img0 = self.img.copy()
[docs] def color_img(self): """Add background color to the image.""" if self.bg == None: self.img.fill((0, 0, 0, 0)) else: self.img.fill(self.bg) self.img0 = self.img.copy()
[docs] def set_background(self, img): """Set background color or transparency.""" if self.bg == None: img.fill((0, 0, 0, 0)) else: img.fill(self.bg)
[docs] def load_img(self): """Load the image file.""" module = sys.modules['__main__'] path, name = os.path.split(module.__file__) path = os.path.join(path, self.file) img = pygame.image.load(path) self.img0 = pygame.Surface(img.get_size(), flags=SRCALPHA) self.set_background(self.img0) self.img0.blit(img, (0, 0)) self.img = pygame.transform.smoothscale(self.img0, self.rect.size)
[docs] def calculate_pos(self, options): """Calculate the next node position.""" if self.id > 0 and 'pos' not in options: last = App.scene.nodes[-1].rect x = self.pos[0] + self.dir[0] * (last.size[0] + self.gap[0]) y = self.pos[1] + self.dir[1] * (last.size[1] + self.gap[1]) self.pos = x, y Node.options['pos'] = x, y
[docs] def render_label(self): """Create and render the node label.""" col, size = Node.label font = pygame.font.Font(None, size) self.label_img = font.render(str(self), True, col) self.label_rect = self.label_img.get_rect() self.label_rect.bottomleft = self.rect.topleft
[docs] def do_event(self, event): """React to events happening for focus node.""" mods = pygame.key.get_mods() if event.type == MOUSEBUTTONDOWN: # click in resize button r = Rect(0, 0, 7, 7) r.bottomright = self.rect.bottomright if r.collidepoint(event.pos) and self.resizable: Node.resizing = True else: Node.moving = True elif event.type == MOUSEMOTION: # resize the node if Node.resizing: dx, dy = event.rel if mods & KMOD_LALT: self.rect.inflate_ip(2*dx, 2*dy) else: self.rect.width += dx self.rect.height += dy self.rect.normalize() #if self.file != '': if isinstance(self, (Ellipse, Rectangle)): self.render() else: self.img = pygame.transform.smoothscale(self.img0, self.rect.size) elif event.type == MOUSEBUTTONUP: Node.resizing = False Node.moving = False
def update(self): pass
[docs] def draw(self): """Draw the node and optionally the outline, label and focus.""" if self.visible: App.screen.blit(self.img, self.rect) if App.debug & DBG_OUTLINE: pygame.draw.rect(App.screen, Node.outline[0], self.rect, Node.outline[1]) if App.debug & DBG_LABELS: App.screen.blit(self.label_img, self.label_rect) if self in App.scene.selection: col, d = Node.selection pygame.draw.rect(App.screen, col, self.rect, d) if self == App.scene.focus: pygame.draw.rect(App.screen, Node.focus[0], self.rect, Node.focus[1]) if self.resizable: r = Rect(0, 0, 7, 7) r.bottomright = self.rect.bottomright pygame.draw.rect(App.screen, Node.focus[0], r, Node.focus[1])
def double_click(self): App.scene.set_status(f'double-click in {self}') print('double-click in', self) def triple_click(self): App.scene.set_status(f'triple-click in {self}') print('triple-click in', self) def __str__(self): return self.__class__.__name__ + str(self.id)
[docs]class TextObj: """Create a text surface image.""" options = { 'fontname': None, 'fontsize': 24, 'fontcolor': Color('black'), 'italic': False, 'bold': False, 'underline': False, 'width': 300, 'align': 0, # 0=left, 1=center, 2=right 'bg': None, 'x': 0, } def __init__(self, text='Text', **options): """Instantiate and render the text object.""" # update existing Text options, without adding new ones for k in options: if k in TextObj.options: TextObj.options[k] = options[k] self.__dict__.update(TextObj.options) self.text = text self.set_font() self.render_text()
[docs] def set_font(self): """Set the font and its properties.""" self.font = pygame.font.Font(self.fontname, self.fontsize) self.font.set_bold(self.bold) self.font.set_italic(self.italic) self.font.set_underline(self.underline)
[docs] def render_text(self): """Render the text into an image.""" img = self.font.render(self.text, True, self.fontcolor, self.bg) self.rect = img.get_rect() self.align_image(img)
def align_image(self, img): w, h = self.font.size(self.text) w0 = self.width self.x = 0 if w0 > 0: self.x = (0, (w0-w)//2, (w0-w))[self.align] self.img = pygame.Surface((w0, h)) if self.bg != None: self.img.fill(self.bg) self.img.blit(img, (self.x, 0)) self.rect.size = self.img.get_size() else: self.img = img
[docs]class Text(Node): """Create a text object horizontal and vertical alignement.""" def __init__(self, text='Text', **options): super().__init__(**options) self.txt = TextObj(text, **options) self.rect.size = self.txt.rect.size self.img = self.txt.img
[docs]class TextLines(Node): options = { 'interline': 1, } def __init__(self, text, **options): super().__init__(**options) self.set_options(TextLines, options) self.text = text self.lines = text.split('\n') self.line0 = TextObj(self.lines[0], **options) self.h = self.line0.font.get_linesize() n = len(self.lines) self.rect.width = self.line0.width self.rect.height = (n-1)*self.interline*self.h+self.h self.img = pygame.Surface(self.rect.size, flags=SRCALPHA) bg = TextObj.options['bg'] if bg != None: self.img.fill(bg) self.render() def render(self): for i, line in enumerate(self.lines): txt = TextObj(line) y = self.interline * self.h * i self.img.blit(txt.img, (0, y))
[docs]class EditableTextObj(TextObj): """Create keyboard and mouse-editable text with cursor and selection.""" cursor_style = Color('red'), 2 # cursor color and width selection_style = Color('pink'), 0 blink_rate = 600, 400 # interval, on_time def __init__(self, text='Text', cmd='', **options): super().__init__(text, **options) # Set cursor index to the end of text string self.cmd = cmd self.set_text(text) def set_text(self, text): self.text = text self.i = len(self.text) self.i2 = self.i self.set_char_positions() self.render()
[docs] def set_char_positions(self): """Make a list of all character positions.""" self.char_positions = [0] for i in range(len(self.text)): w, h = self.font.size(self.text[:i+1]) self.char_positions.append(w)
[docs] def get_char_index(self, position): """Return the character index for a given position.""" for i, pos in enumerate(self.char_positions): if position <= pos: return i # if not found return the highest index return i
[docs] def move_cursor(self, d): """Move the cursor by d characters, and limit to text length.""" mod = pygame.key.get_mods() n = len(self.text) self.i = min(max(0, self.i+d), n) if mod & KMOD_META: self.i = n if d == 1 else 0 if mod & KMOD_ALT: while (0 < self.i < n) and self.text[self.i] != ' ': self.i += d if not mod & KMOD_SHIFT: self.i2 = self.i
[docs] def get_selection(self): """Get ordered tuple of selection indices.""" if self.i < self.i2: return self.i, self.i2 else: return self.i2, self.i
[docs] def copy_text(self): """Copy text to Scene.text buffer.""" i, i2 = self.get_selection() text = self.text[i:i2] App.scene.text = text
[docs] def cut_text(self): """Cut text and place copy in Scene.text buffer.""" self.copy_text() self.insert_text('')
[docs] def insert_text(self, text): """Insert text at the cursor position or replace selection.""" i, i2 = self.get_selection() text1 = self.text[:i] text2 = self.text[i2:] self.text = text1 + text + text2 self.set_char_positions() self.i = i + len(text) self.i2 = self.i
[docs] def select_word(self): """Select word at current position.""" i = i2 = self.i n = len(self.text) while (0 < i < n) and self.text[i] != ' ': i -= 1 while (0 < i2 < n) and self.text[i2] != ' ': i2 += 1 self.i = i2 self.i2 = i+1 if i<n and self.text[i]==' ' else i
[docs] def select_all(self): """Select the whole text.""" self.i = len(self.text) self.i2 = 0
[docs] def do_event(self, event): """Move cursor, handle selection, add/backspace text, copy/paste.""" if event.type == KEYDOWN: if event.key == K_RETURN: try: exec(self.cmd) except: print(f'cmd error in {self}') elif event.key == K_BACKSPACE: # delete previous charactor or selection if self.i == self.i2: self.i = max(0, self.i-1) self.insert_text('') elif event.key in (K_TAB, K_UP, K_DOWN, K_LCTRL, K_LMETA, K_LALT, K_LSHIFT): pass elif event.key == K_LEFT: self.move_cursor(-1) elif event.key == K_RIGHT: self.move_cursor(1) elif not (event.mod & KMOD_META + KMOD_CTRL): self.insert_text(event.unicode) elif event.key == K_x and event.mod & KMOD_META: self.cut_text() elif event.key == K_c and event.mod & KMOD_META: self.copy_text() elif event.key == K_v and event.mod & KMOD_META: self.insert_text(App.scene.text) elif event.key == K_a and event.mod & KMOD_META: self.select_all() self.render() elif event.type == MOUSEBUTTONDOWN: pos = event.pos[0] - self.rect.left - self.x if pos < 3: pos = 0 self.i = self.get_char_index(pos) if not pygame.key.get_mods() & KMOD_SHIFT: self.i2 = self.i elif event.type == MOUSEMOTION and event.buttons[0]: pos = event.pos[0] - self.rect.left - self.x if pos < 3: pos = 0 self.i = self.get_char_index(pos) self.render()
[docs] def render(self): """Render cursor, selection and text to an image.""" h = self.font.get_height() p = self.char_positions[self.i] p2 = self.char_positions[self.i2] self.cursor_rect = Rect(p, 0, 2, h) if p2 < p: p, p2 = p2, p self.selection_rect = Rect(p, 0, p2-p, h) w, h = self.font.size(self.text) img = pygame.Surface((w+2, h)) col, d = self.selection_style pygame.draw.rect(img, col, self.selection_rect, d) txt = self.font.render(self.text, True, self.fontcolor) img.blit(txt, (0, 0)) col, d = self.cursor_style pygame.draw.rect(img, col, self.cursor_rect) self.align_image(img)
[docs]class EditableText(Node): """Create an editable text node.""" def __init__(self, text='Text', **options): super().__init__(**options) self.txt = EditableTextObj(text, **options) self.rect.size = self.txt.rect.size self.txt.rect = self.rect self.img = self.txt.img self.rect.height = self.txt.font.get_height()
[docs] def do_event(self, event): self.txt.do_event(event) self.img = self.txt.img
[docs] def draw(self): # self.txt.draw() Node.draw(self) if self == App.scene.focus: t = pygame.time.get_ticks() interval, on_time = EditableTextObj.blink_rate if (t % interval) < on_time: col, d = EditableTextObj.cursor_style rect = self.txt.cursor_rect.move(self.rect.topleft) rect.move_ip(self.txt.x, 0) pygame.draw.rect(App.screen, Color('blue'), rect)
[docs] def double_click(self): """Select the current word.""" self.txt.select_word()
def triple_click(self): self.txt.select_all()
[docs]class Button(Node): """Create a button object with command.""" options = { 'border': 2, 'bg': Color('gray'), 'size': (160, 40), 'autosize': False, 'align': 1, 'state': False, } def __init__(self, text='Button', cmd='', **options): super().__init__(**options) self.set_options(Button, options) self.cmd = cmd self.label = TextObj(text, bg=None, **options) self.render() def render(self): self.img.fill(Color('lightblue')) self.label.render_text() w, h = self.rect.size self.label.rect.center = w//2, h//2 self.img.blit(self.label.img, self.label.rect)
[docs] def do_event(self, event): super().do_event(event) if event.type == MOUSEBUTTONDOWN: self.state = not self.state try: exec(self.cmd) except: print('cmd error') if self.state: self.label.text = 'ON' else: self.label.text = 'OFF' self.render()
[docs]class Toggle: """Add toggle button behavior.""" def switch_state(self): self.state = not self.state try: exec(self.cmd) except: print('cmd error') self.render() def do_event(self, event): if event.type == MOUSEBUTTONDOWN: self.switch_state() elif event.type == KEYDOWN: if event.key == K_RETURN: self.switch_state()
[docs]class Checkbox(Toggle, Node): options = { 'state': False, 'style': (Color('blue'), 2), } def __init__(self, **options): super().__init__(**options) self.set_options(Checkbox, options) self.label = TextObj(**options) w, h = self.label.img.get_size() self.size = w+h, h self.rect.size = self.size self.img = pygame.Surface((w+h, h), flags=SRCALPHA) self.state = False self.render() def render(self): col, d = self.style w, h = self.label.img.get_size() a = self.label.font.get_ascent() self.img.blit(self.label.img, (h, 0)) pygame.draw.rect(self.img, (0, 0, 0, 0), Rect(0, 0, a, a)) pygame.draw.rect(self.img, Color('black'), Rect(0, 0, a, a), d) if self.state: pygame.draw.line(self.img, col, (0, 0), (a, a), d) pygame.draw.line(self.img, col, (0, a), (a, 0), d)
[docs]class Radiobutton(Checkbox): def render(self): col, d = self.style w, h = self.label.img.get_size() a = self.label.font.get_ascent() self.img.blit(self.label.img, (h, 0)) pygame.draw.rect(self.img, (0, 0, 0, 0), Rect(0, 0, a, a)) pygame.draw.ellipse(self.img, Color('black'), Rect(0, 0, a, a), 1) if self.state: pygame.draw.ellipse(self.img, Color('black'), Rect(3, 3, a-6, a-6), 0)
[docs]class ListBox(Node): """Show a list of text items.""" options = { 'm': 10, # listbox height 'width': 100, # in pixels 'wrap': False, # cursors wraps around 'align': 0, # 0=left, 1=center, 2=right 'mode': 1, # 0=none, 1=one, 2=multiple selections 'style': (Color('black'), Color('white')), # font color, background color 'sel_style': (Color('white'), Color('blue')), # font color, background color 'fontsize': 24, } def __init__(self, items, i=0, **options): super().__init__(**options) self.set_options(ListBox, options) self.set_list(items) self.font = pygame.font.Font(None, self.fontsize) self.h = self.font.size('fg')[1] self.render()
[docs] def set_list(self, items): """Set items and selection list.""" self.i = 0 self.i0 = 0 # first ListBox item self.i2 = 0 # cursor self.items = items self.n = len(items) self.sel = [0] * self.n
def render(self): w0 = self.width fg, bg = self.style self.img0 = pygame.Surface((w0, self.m * self.h)) self.img0.fill(bg) self.rect.size = self.img0.get_size() for i in range(min(self.m, self.n)): if self.sel[self.i0 + i]: fg, bg = self.sel_style else: fg, bg = self.style text = self.font.render(self.items[self.i0 + i], True, fg) w, h = text.get_size() x = (0, (w0-w)//2, w0-w)[self.align] rect = Rect(0, i * self.h, w0, h) self.img0.fill(bg, rect) self.img0.blit(text, (x, i*self.h)) self.img = self.img0.copy()
[docs] def scroll(self, d): """Scroll listbox up and down.""" i0 = self.i0 n = max(0, self.n - self.m) self.i0 = max(0, min(i0+d, n))
[docs] def move_cursor(self, d): """Move the active cell up or down.""" mod = pygame.key.get_mods() i, n = self.i, self.n # when ALT pressed move a screenful if mod & KMOD_ALT: d *= self.m-1 if self.wrap: i = (i + d) % n else: i = max(0, min(i+d, n-1)) # adjust visible part if i < self.i0: self.i0 = i elif i >= self.i0 + self.m: self.i0 = i - self.m + 1 # when ALT pressed move to begin or end if mod & KMOD_META: if d == 1: i = n - 1 self.i0 = max(0, n - self.m) else: i = 0 self.i0 = 0 self.i = i # self.sel[i] = 1 self.select(i)
def select(self, i): # Select item i mod = pygame.key.get_mods() self.i = i self.item = self.items[i] if self.mode == 1: self.select_all(0) self.sel[i] = 1 elif self.mode == 2: if mod & KMOD_SHIFT: self.sel[i] = 1 elif mod & KMOD_META: self.sel[i] = 1 - self.sel[i] else: self.select_all(0) self.sel[i] = 1 def select_all(self, val): for i in range(self.n): self.sel[i] = val
[docs] def do_event(self, event): if event.type == MOUSEBUTTONDOWN: if event.button == 1 or event.button == 3: x, y = event.pos x -= self.rect.left y -= self.rect.top i = y // self.h + self.i0 i = min(i, self.n-1) self.select(i) # scroll with mouse pad elif event.button == 4: self.scroll(-1) elif event.button == 5: self.scroll(1) elif event.type == KEYDOWN: if event.key == K_DOWN: self.move_cursor(1) elif event.key == K_UP: self.move_cursor(-1) elif event.key == K_RETURN: exec(self.cmd) elif event.key == K_a: if event.mod & KMOD_META and self.mode == 2: self.select_all(1) self.render()
[docs]class ListMenu: """Display a drop-down menu.""" def __init__(self, items, **options): self.items = items
[docs]class SliderObj: """Define a slider object.""" options = { 'x0': 0, 'x1': 100, 'dx': 1, 'size': (100,30), 'orientation': 0, # 0=horizontal, 1=vertical 'slider_style': (Color('blue'), Color('white'), 4, 2), 'slider_size': (10, 20), 'slider_type': 0, # 0=rectangle 1=circle } style_label = (Color('black')) def __init__(self, **options): # update existing SliderObj options, without adding new ones for k in options: if k in SliderObj.options: SliderObj.options[k] = options[k] self.__dict__.update(SliderObj.options) self.x = (self.x1 + self.x0)/2 self.img = pygame.Surface(self.size, flags=SRCALPHA) self.rect = self.img.get_rect() self.font = pygame.font.Font(None, 18) self.render() def render(self): w, h = self.size col, col2, d, d2 = self.slider_style w0, h0 = self.slider_size img_x0 = self.font.render(str(self.x0), True, col) img_x1 = self.font.render(str(self.x1), True, col) img_x = self.font.render(f'{self.x:.1f}', True, col) rect_x0 = img_x0.get_rect() rect_x1 = img_x1.get_rect() rect_x = img_x.get_rect() if self.orientation == 0: rect_x0.bottomleft = (0, h) else: rect_x0.bottomleft = (0, h) if self.orientation == 0: rect_x1.bottomright = w, h else: rect_x1.topleft = 0, 0//2 if self.orientation == 0: rect_x.midbottom = w//2, h else: rect_x.midleft = 0, h//2 if self.orientation == 0: self.slider_rect = Rect(0, 0, w0, h0) else: self.slider_rect = Rect(0, 0, h0, w0) if self.orientation == 0: self.slider_rect.topleft = (self.x-self.x0) / (self.x1-self.x0) * (w-w0), 0 else: self.slider_rect.topright = w, (self.x1-self.x) / (self.x1-self.x0) * (h-w0) self.img.fill((0, 0, 0, 0)) self.img.blit(img_x0, rect_x0) self.img.blit(img_x1, rect_x1) self.img.blit(img_x, rect_x) if self.orientation == 0: p0 = 0, h0//2 p1 = w, h0//2 else: p0 = w-h0//2, 0 p1 = w-h0//2, h pygame.draw.line(self.img, col, p0, p1, d) if self.slider_type == 0: pygame.draw.rect(self.img, col2, self.slider_rect) pygame.draw.rect(self.img, col, self.slider_rect, d2) else: pygame.draw.ellipse(self.img, col2, self.slider_rect) pygame.draw.ellipse(self.img, col, self.slider_rect, d2) def do_event(self, event): keys = {K_DOWN:-1, K_LEFT:-1, K_UP:1, K_RIGHT:1} if event.type == KEYDOWN: if event.key in keys: dx = keys[event.key] * self.dx if event.mod & KMOD_ALT: dx *= 10 if event.mod & KMOD_META: dx *= 100 self.x = max(self.x0, min(self.x + dx, self.x1)) self.render() if event.type == MOUSEMOTION and event.buttons[0] == 1: dx, dy = event.rel w, h = self.size x = self.x1 - self.x0 if self.orientation == 0: self.x += (dx/w)*x else: self.x += (-dy/h)*x self.x = max(self.x0, min(self.x, self.x1)) self.render()
[docs]class Slider(Node): def __init__(self, **options): super().__init__(**options) self.slider = SliderObj(**options) self.slider.rect = self.rect self.img = self.slider.img
[docs] def do_event(self, event): self.slider.do_event(event)
[docs]class NumInput(EditableText): options = { 'min': 0, 'max': 100, 'inc': 1, 'val': 0, } def __init__(self, **options): self.set_options(NumInput, options) super().__init__(str(self.val), align=2, **options)
[docs] def do_event(self, event): super().do_event(event) try: val = int(self.txt.text) except: val = self.min self.val = max(self.min, min(val, self.max)) self.txt.set_text(str(self.val)) if event.type == KEYDOWN: keys = {K_DOWN:-1, K_UP:1} if event.key in keys: inc = keys[event.key] * self.inc if event.mod & KMOD_ALT: inc *= 10 if event.mod & KMOD_META: inc *= 100 self.val = max(self.min, min(self.val + inc, self.max)) self.txt.set_text(str(self.val))
[docs]class Spinbox(Node): """Input a number.""" options = { 'min': 0, 'max': 10, 'inc': 1, 'val': 5, 'lbl': 'Spinbox', 'w': (100, 100) } def __init__(self, **options): super().__init__(**options) self.set_options(Spinbox, options) self.label = TextObj(self.lbl, **options) self.value = EditableTextObj(str(self.val), bg=Color('cyan'), align=2) x0, x1 = self.w h = self.label.img.get_size()[1] self.img = pygame.Surface((x0+x1, h)) self.img.set_colorkey(Color('white')) self.rect.size = self.img.get_size() self.render() def render(self): self.img.fill(Color('white')) self.img.blit(self.label.img, (0, 0)) self.img.blit(self.value.img, (self.w[0], 0)) self.img0 = self.img.copy()
[docs] def do_event(self, event): if event.type == KEYDOWN: if event.key == K_RETURN: exec(self.cmd) elif event.key in (K_RIGHT, K_UP): self.val = min(self.max, self.val + self.inc) elif event.key in (K_LEFT, K_DOWN): self.val = max(self.min, self.val - self.inc) self.value.text = str(self.val) self.value.render() self.render()
[docs]class Rectangle(Node): """Draw a rectangle on the screen.""" options = { 'fg': Color('green'), 'bg': Color('black'), 'thickness': 2} def __init__(self, **options): super().__init__(**options) self.set_options(Rectangle, options) self.render() def render(self): self.img0 = pygame.Surface(self.rect.size, flags=SRCALPHA) if self.fg != None: pygame.draw.rect(self.img0, self.fg, Rect(0, 0, *self.rect.size), 0) pygame.draw.rect(self.img0, self.bg, Rect(0, 0, *self.rect.size), self.thickness) self.img = self.img0.copy()
[docs]class Ellipse(Rectangle): """Draw an ellipse on the screen.""" def render(self): self.img0 = pygame.Surface(self.rect.size, flags=SRCALPHA) if self.fg != None: pygame.draw.ellipse(self.img0, self.fg, Rect(0, 0, *self.rect.size), 0) pygame.draw.ellipse(self.img0, self.bg, Rect(0, 0, *self.rect.size), self.thickness) self.img = self.img0.copy()
[docs]class Board(Node): """Draw a mxn board grid with m lines and n columns. m, n number of cells (row, col) i, j index of cell (row, col) dx, dy size of cell Num numeric matrix Num0 initial numeric matrix Col color matrix """ options = { 'm': 4, 'n': 4, 'i': 0, 'j': 0, 'dx': 40, 'dy': 40, 'selection': [], 'grid': (Color('black'), 1), # grid color and thickness 'selection_col': (Color('green'), 2), # selection color and thickness 'focus_col': (Color('red'), 2), # current focus cell (i, j) 'images': [None], 'colors': [Color('white'), Color('gray')], 'folder': '', 'drag': False, 'centered': False, # grid is centered as in Go 'keys': '0123456789', # keys which are accepted } def __init__(self, **options): super().__init__(**options) # update existing Board options, without adding new ones for k in options: if k in Board.options: Board.options[k] = options[k] self.__dict__.update(Board.options) # update board size self.size = self.n * self.dx, self.m * self.dy self.rect.size = self.size self.img = pygame.Surface(self.rect.size, flags=SRCALPHA) self.Num = np.zeros((self.m, self.n), dtype='int8') self.Num0 = self.Num.copy() self.Col = self.Num.copy() self.font = pygame.font.Font(None, self.dy) self.render()
[docs] def set_Num(self, s): """Load Num table from a string.""" s = s.replace('.', '0') lines = s.split() m = len(lines) n = len(lines[0]) self.Num = np.zeros((m, n), dtype='int8') for i in range(m): for j in range(n): self.Num[i, j] = int(lines[i][j]) self.Num0 = self.Num.copy() self.render()
# def switch_Num(self, dir): # i1, j1 # i2 = i + di # j2 = j + dj # if 0 < i2 < n: # tmp = self.Num[i1, j1] # self.Num[i1, j1] = self.Num[i2, j2] # self.Num[i2, j2] = tmp def set_checkerboard(self): self.Col = np.fromfunction(lambda i, j: (i+j)%2, (self.m, self.n), dtype='int8') def load_images(self): name = type(self).__name__ module = sys.modules['__main__'] path, name = os.path.split(module.__file__) path = os.path.join(path, self.folder) dir = os.listdir(path) dir.sort() for file in dir: img_path = os.path.join(path, file) root, ext = os.path.splitext(img_path) if ext in ['.png', '.jpg']: img = pygame.image.load(img_path) img = pygame.transform.smoothscale(img, (self.dx, self.dy)) self.images.append(img)
[docs] def render_colors(self): """Render the background colors.""" for i in range(self.m): for j in range(self.n): rect = self.get_rect(i, j) col = self.colors[self.Col[i, j]] if col != None: pygame.draw.rect(self.img, col, rect)
[docs] def render_grid(self): """Render the grid lines.""" col = self.grid[0] d = self.grid[1] x0, y0 = 0, 0 dx, dy = self.dx, self.dy x1, y1 = self.rect.size m = self.m+1 n = self.n+1 if self.centered: m, n = m-1, n-1 x0 += dx//2 y0 += dy//2 x1 -= dx//2 y1 -= dy//2 for i in range(m): y = y0 + i * dy pygame.draw.line(self.img, col, (x0, y), (x1, y), d) for j in range(n): x = x0 + j * dx pygame.draw.line(self.img, col, (x, y0), (x, y1), d)
[docs] def render_num(self): """Draw number.""" for i in range(self.m): for j in range(self.n): k = self.Num[i, j] if k != 0: img = self.font.render(str(k), True, Color('black')) rect = img.get_rect() rect.center = self.get_rect(i, j).center self.img.blit(img, rect)
[docs] def render_tile(self): """Draw number.""" for i in range(self.m): for j in range(self.n): k = self.Num[i, j] if k != 0: img = self.images[k] rect = img.get_rect() rect.center = self.get_rect(i, j).center self.img.blit(img, rect)
def render_sel(self): col = self.selection_col[0] d = self.selection_col[1] for i, j in self.selection: pygame.draw.rect(self.img, col, self.get_rect(i, j), d) def render_focus(self): col = self.focus_col[0] d = self.focus_col[1] pygame.draw.rect(self.img, col, self.get_rect(self.i, self.j), d)
[docs] def render(self): """Render the whole board.""" self.img = pygame.Surface(self.rect.size, flags=SRCALPHA) self.render_colors() self.render_grid() self.render_num() self.render_sel() self.render_focus() if len(self.images) > 1: self.render_tile()
def get_rect(self, i, j): dx = self.dx dy = self.dy return Rect(j*dx, i*dy, dx, dy)
[docs] def get_index(self, x, y): """Get index (i, j) from mouse position (x, y).""" x = x - self.rect.left y = y - self.rect.top i = y // self.dy j = x // self.dx return i, j
[docs] def do_event(self, event): """React to events.""" mods = pygame.key.get_mods() if event.type == MOUSEBUTTONDOWN: self.drag = True i, j = self.get_index(*event.pos) k = self.Num[i, j] self.drag_rect = self.get_rect(i, j) # self.drag_img = self.images[k] if mods & KMOD_META: self.selection.append((i, j)) else: self.selection = [(i, j)] print(self.selection) self.render() elif event.type == MOUSEMOTION: if self.drag: self.drag_rect.move_ip(event.rel) elif event.type == MOUSEBUTTONUP: self.drag = False elif event.type == KEYDOWN: if event.key in self.dirs: if self.selection == []: self.selection = [(0, 0)] i, j = self.selection[0] dj, di = self.dirs[event.key] i += di j += dj i = max(0, min(i, self.m-1)) j = max(0, min(j, self.n-1)) self.selection = [(i, j)] self.i = i self.j = j self.render() if event.unicode in self.keys: print(event.unicode) if self.Num0[self.i, self.j] == 0: self.Num[self.i, self.j] = int(event.unicode) self.render() elif event.key == K_RETURN: k = self.Num[self.i, self.j] k = (k+1) % 3 self.Num[self.i, self.j] = k self.render()
[docs]class Sudoku(Board): """Create a sudoko game board.""" def __init__(self, **options): super().__init__(m=9, n=9, **options)
[docs] def render_grid(self): """Override the grid lines.""" super().render_grid() col = self.grid[0] d = self.grid[1] * 2 x1, y1 = self.rect.size for x in [3 * self.dx, 6 * self.dx]: pygame.draw.line(self.img, col, (x, 0), (x, y1), d) for y in [3 * self.dy, 6 * self.dy]: pygame.draw.line(self.img, col, (0, y), (x1, y), d)
[docs]class Chess(Board): """Create a sudoko game board.""" def __init__(self, **options): super().__init__(m=8, n=8, colors=[Color('white'), Color('brown')], centered=False, **options) self.set_checkerboard() self.folder='chess' self.load_images() self.reset() def reset(self): self.Num = np.zeros((self.m, self.n), dtype='int8') self.Num[0, :] = [3, 5, 7, 9, 11, 7, 5, 3] self.Num[1, :] = 1 self.Num[6, :] = 2 self.Num[7, :] = [4, 6, 8, 10, 12, 8, 6, 4] self.render()
[docs]class Go(Board): def __init__(self, **options): super().__init__(m=19, n=19, dx=10, dy=10, centered=True, colors=[Color('beige')], **options)
[docs] def render(self): """Render the Go board and add extra dots on certain intersections.""" super().render() pts = [] if self.n == 19: pts = [(3, 3), (9, 3), (16, 3), (3, 9), (9, 9), (16, 9), (3, 16), (9, 16), (16, 16)] for (i, j) in pts: x = j * self.dx + self.dx//2 y = i * self.dy + self.dy//2 pygame.draw.circle(self.img, Color('black'), (x, y), 3)
[docs]class Puzzle(Node): """Take an image and create a puzzle.""" def __init__(self, div=(3, 3), **options): super().__init__(**options) self.divide_img(*div) def divide_img(self, m, n): (w, h) = self.img.get_size() self.images = [] w0 = w//n h0 = h//m for i in range(m): y = i * h0 for j in range(n): x = j * w0 img = self.img.subsurface(Rect(x, y, w0, h0)) self.images.append(img) if (i, j) == (0, 0): node = Node(size=(w0, h0), dir=(1, 0), resizable=False) else: node = Node(size=(w0, h0), dir=(0.1, 0.1)) node.img = img node.img0 = img
if __name__ == '__main__': app = App() Scene('Introduction') TextLines('''An app can have multiple scenes\n cmd+s - goes to the next scene cmd+shift+s - goes to the previous scene cmd+f - toggle full screen cmd+r - toggle resizable cmd+g - toggle no-frame''') Scene('Debugging') TextLines('''Debug shortcuts\n cmd+o - show outline cmd+l - show labels cmd+e - show events cmd+q - quit the app cmd+h - hide window cmd+p - save screen shot to current folder''') Scene('Create objects') TextLines('''Create objects\n ctrl+r - new Rectangle ctrl+e - new Ellipse ctrl+t - new Text ctrl+n - new Node''') Rectangle() Ellipse(fg=Color('pink'), bg=Color('magenta'), thickness=10) Scene('Scene shortcuts', shortcuts={(K_1, KMOD_NONE):'print(1)'}) Text('Pressing "1" prints 1 to the console') Scene('Scene shortcuts', shortcuts={(K_1, KMOD_NONE):'print(1111111)'}, remember=False) Text('Pressing "1" prints 1111111 to the console') Scene(caption='ListBox') ListBox(['Charlie', 'Daniel', 'Tim', 'Jack'], cmd='print(self.item)') cities = ['Amsterdam', 'Berlin', 'Cardiff', 'Dublin', 'Edinbourgh', 'Fargo', 'Greenwich', 'Harrington', 'Melbourne'] ListBox(cities, dir=(1, 0), cmd='App.scene.set_status(self.item)') Scene(caption='Node size') Node(size=(40, 40), dir=(1, 1)) Node(size=(100, 30)) Node(size=(60, 60)) Node(size=(20, 20)) Scene(caption='Ellipse and Rectangle') Ellipse(fg=Color('yellow')) Rectangle(fg=Color('pink')) Ellipse(fg=Color('green')) Rectangle(fg=Color('orange')) Scene(caption='Board game - selection with mouse and arrow keys') Board(dir=(1, 0)) Board() Board() Scene(caption='Board size') Board(dir=(1, 0)) Board(m=5) Board(n=5) Scene(caption='Board checker color') Board(dir=(1, 0), m=4, n=4) Board() Board() Scene(caption='Board - Go') Go(dir=(1, 0)) Go() # Chess(dx=20, dy=20) Scene(caption='Board - Memory puzzle') b = Board(m=4, n=4, dx=50, dy=50, centered=False, folder='../animals') b.images = [] b.load_images() b.Num = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]]) b.render() Board(dir=(1, 0)) Scene(caption='Board - Set colors at random') b = Board() b.colors = [Color('red'), Color('green'), Color('blue'), Color('yellow'), Color('pink'), Color('lightblue'), Color('darkred')] for i in range(b.m): for j in range(b.n): b.Col[i, j] = np.random.randint(0, len(b.colors)) b.render() Scene(caption='Board - Puzzle') Puzzle(file='../../images/cat.jpg', size=(200, 200)) Scene(caption='Board - Puzzle', selection_surround=True) Puzzle(file='../../images/river.jpg', size=(200, 200)) Scene('Nodes with images') Node(file='../animals/cat-icon.png', size=(100, 100), dir=(1, 0)) Node(file='../animals/dog-icon.png', bg=Color('yellow')) Node(file='../animals/cow-icon.png') Node(file='', bg=None) Scene(caption='Buttons', bg=Color('beige')) Text('Buttons') Scene(caption='Board - number puzzle') b = Board(m=4, n=4) b.colors = (None, Color('red'))*8 b.Num = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 0]]) b.Num = np.arange(16).reshape((4, 4)) b.render() Scene('TextObj') Text('TextObj', size=(150, 25)) Text('underline', underline=True) Text('italic', italic=True) Text('bold', bold=True) Text('red', fontcolor=Color('red')) Text('size=48', fontsize=48, keep=True) names = ['Charlie', 'Daniel', 'Tim', 'Jack'] cities = ['Amsterdam', 'Berlin', 'Cardiff', 'Dublin', 'Edinbourgh', 'Fargo', 'Greenwich', 'Harrington', 'Melbourne', 'New York', 'Oslo', 'Paris'] constants = dir(pygame.locals) Scene('ListBox') Text('Text alignement: left, center, right', fontsize=24, width=0, fontcolor=Color('black'), italic=False, bold=False, underline=False) ListBox(names, cmd='print(self.item)') ListBox(cities, dir=(1, 0), wrap=True, align=1, mode=1) ListBox(constants, width=300, align=2, wrap=False, mode=2) Scene('ListBox') Text('Single item selection') ListBox(names, cmd='print(self.item)', width=100, align=0, mode=1) ListBox(cities, dir=(1, 0), wrap=True) ListBox(constants, width=300, align=2, wrap=False) Scene('ListBox') Text('Multiple item selection') ListBox(names, cmd='print(self.item)', width=100, align=0, mode=2) ListBox(cities, dir=(1, 0), wrap=True) ListBox(constants, width=300, align=2, wrap=False) Scene('Checkbox') for x in ('Monday', 'Tuesday', 'Wednesday'): Checkbox(text=x, cmd='print(self, self.state)') Radiobutton.pos = (200, 20) for x in ('Java', 'Python', 'C++'): Radiobutton(text=x) Scene('Multi-line text') TextLines('align=0\nThis is text is extending over\nmultiple lines', align=0, width=400) TextLines('align=1\nThis is text is extending over\nmultiple lines', align=1) TextLines('align=2\nThis is text is extending over\nmultiple lines', align=2) Scene('Multi-line text') TextLines('This is text extending\nover multiple lines') TextLines('interline=1.5\nThis is text is extending over\nmultiple lines', align=1, interline=1.5) TextLines('interline=0.8\nThis is text is extending over\nmultiple lines', align=2, interline=0.8) #\nScene('ListMenu') # ListBox(['Charlie', 'Daniel', 'Tim', 'Jack'], cmd='print(self.item)') Scene('Rectangles') Rectangle(fg=Color('yellow'), thickness=10) Rectangle(fg=Color('cyan')) Rectangle(fg=None) Scene('TextEdit - editable text') EditableText('This is left-aligned editable text', align=0, width=400, fontsize=24) EditableText('This is centered editable text', align=1) EditableText('This is right-aligned editable text', align=2) EditableText('This text has a cmd fonction', cmd='print(self.text)') Scene('Text - alignement and autosize') Text('left (align=0)', align=0, width=300) Text('center (align=1)', align=1) Text('right (align=2)', align=2) Text('fontcolor=blue', fontcolor=Color('blue')) Text('background=cyan', bg=Color('cyan')) Text('autosize (width=0)', width=0) Scene('Buttons') Button(file='../../images/ui/blue_button00.png', cmd='print(self, self.state)') Button('Start', cmd='print(self, self.state)') Button('Stop') Node(file='../../images/ui/green_button00.png', cmd='print(123)', pos=(200, 20)) Node(file='../../images/ui/red_button00.png') Node(file='../../images/ui/blue_boxCheckmark.png', size=(38, 36)) Node(file='../../images/ui/blue_boxCross.png') Scene('Slider') Text('Horizontal slider') Slider(size=(100, 40)) Slider(size=(200, 40), x0=-50, x1=50, dx=10) Slider(size=(300, 30), x0=0, x1=10, dx=1, slider_type=1, slider_size=(14, 14)) Scene('Slider') Text('Vertical slider') Slider(orientation=1, size=(40, 200), dir=(1, 0)) Slider(size=(40, 100)) Slider(size=(40, 150)) Scene('Spinbox') Spinbox() Spinbox(val=7) Spinbox(lbl='max=100', max=100, fontsize=36) Spinbox(lbl='inc=10', inc=10) Scene('Numeric input') Text('Enter numeric input. Use UP/DOWN keys or write.', bg=None) NumInput(width=100, bg=Color('cyan')) NumInput(inc=2) NumInput(inc=5) app.run()