Skip to content

Instantly share code, notes, and snippets.

@kdougan
Last active October 17, 2022 01:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kdougan/d9fb2ac51500a872d613e64959f2f1eb to your computer and use it in GitHub Desktop.
Save kdougan/d9fb2ac51500a872d613e64959f2f1eb to your computer and use it in GitHub Desktop.
from dataclasses import dataclass
import re
import pygame
from pygame import Vector2 as vec2
class Game:
def __init__(self):
pygame.init()
self.win_size = vec2(500)
self.screen_scale = 2
self.win = pygame.display.set_mode(self.win_size)
self.clock = pygame.time.Clock()
self.running = False
self.font = pygame.font.SysFont('Arial', 12)
self.text_area = TextArea(
self.font, 10, 230,
'''This creates a new Surface with the specified text rendered on it.\n\npygame provides no way to directly draw text on an existing Surface: instead you must use Font.render() to create an image (Surface) of the text, then blit this image onto another Surface''',
padding=8
)
@property
def screen_size(self) -> vec2:
return self.win_size/self.screen_scale
def run(self) -> None:
self.running = True
while self.running:
pygame.display.set_caption(
'playground '
f'- {int(self.clock.get_fps())}fps '
)
self.events()
self.update()
self.render()
def events(self) -> None:
for event in pygame.event.get():
if (event.type == pygame.QUIT or
(event.type == pygame.KEYDOWN and
event.key == pygame.K_ESCAPE)):
self.running = False
self.text_area.handle_event(event)
def update(self) -> None:
dt = self.clock.tick(60)*0.001
self.text_area.update(dt)
def render(self) -> None:
self.win.fill((30,20,30))
screen = pygame.Surface(self.win_size/self.screen_scale, pygame.SRCALPHA)
self.text_area.render(screen)
self.win.blit(pygame.transform.scale(screen, self.win_size), vec2())
pygame.display.update()
class TextArea:
def __init__(self, font:pygame.font.Font, pos:vec2, size:vec2, text:str='', padding:int=0):
self.font = font
self.pos = vec2(pos)
self.size = vec2(size)
self.padding = padding
self.text = text
self._text = text
self.surf = None
self.focused = True
self.cursor_surf = self.font.render('|', False, (200,200,200))
self.cursor_timer = 0
self.cursor_rate = 0.5
self.cursor_vis = False
self.cursor_pos = vec2()
self.repeat_event = None
self.repeat_timer = 0
self.repeat_rate = 0.05
self.repeat_delay = 1
self.is_repeating = False
self.set_text(self.text)
def handle_event(self, event:pygame.event.Event) -> None:
if (event.type == pygame.MOUSEBUTTONDOWN and
event.button == 1):
self.focused = pygame.Rect(self.pos, self.size).collidepoint(vec2(event.pos)/2)
if event.type == pygame.KEYDOWN:
self.repeat_event = event
self.repeat_timer = 0
if event.key == pygame.K_BACKSPACE:
self.set_text(self.text[:-1])
elif re.match(r'[\s\S]', event.unicode):
self.set_text(f'{self.text}{event.unicode}')
if (event.type == pygame.KEYUP and
self.repeat_event and
event.key == self.repeat_event.key):
self.repeat_event = None
self.is_repeating = False
def update(self, dt:float) -> None:
self.cursor_timer += dt
if self.cursor_timer >= self.cursor_rate:
self.cursor_timer = 0
self.cursor_vis = not self.cursor_vis
if self.repeat_event:
self.repeat_timer += dt
if self.repeat_timer >= self.repeat_delay:
if not self.is_repeating:
self.is_repeating = True
if self.is_repeating and self.repeat_timer >= self.repeat_rate:
self.handle_event(self.repeat_event)
self.repeat_timer = 0
def set_text(self, text:str) -> None:
self.text = re.sub(r'[\n\r]', '\n', text)
surf = pygame.Surface(self.size, pygame.SRCALPHA)
line_height = self.font.get_linesize()
y = self.padding
line = ''
for i, char in enumerate(self.text):
if re.match(r'[\n\r]', char):
surf.blit(self.font.render(line, False, (200,200,200)), vec2(self.padding, y))
line = ''
y += line_height
continue
line = f'{line}{char}'
if vec2(self.font.size(line)).x > self.size.x - (self.padding*2):
parts = re.split(r'(\W)', line[::-1], maxsplit=1)
if len(parts) < 2:
word = f'{line[:-2]}-'
surf.blit(self.font.render(word, False, (200,200,200)), vec2(self.padding, y))
line = line[-2:]
y += line_height
else:
rev = parts[2][::-1]
word = rev if re.match(r'\s', parts[1]) else f'{rev}{parts[1]}'
surf.blit(self.font.render(word, False, (200,200,200)), vec2(self.padding, y))
line = parts[0][::-1]
y += line_height
if line:
surf.blit(self.font.render(line, False, (200,200,200)), vec2(self.padding, y))
self.cursor_pos = vec2(vec2(self.font.size(line)).x + self.padding, y)
self.surf = surf
def render(self, surf:pygame.Surface) -> None:
surf.blit(self.surf, self.pos)
if self.focused:
if self.cursor_vis:
surf.blit(self.cursor_surf, self.pos + self.cursor_pos)
pygame.draw.rect(surf, (200,0,200,100), (self.pos+vec2(self.padding), self.size-vec2(self.padding*2)), 1, 2)
pygame.draw.rect(surf, (0,200,200), (self.pos, self.size), 1, 4)
Game().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment