"""
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 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 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 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 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 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()