Skip to content

Commit

Permalink
Merge pull request #2588 from gresm/multiline-more-examples
Browse files Browse the repository at this point in the history
Multiline more examples
  • Loading branch information
Starbuck5 authored Dec 13, 2023
2 parents c291e9e + 79c803e commit b560863
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 106 deletions.
275 changes: 177 additions & 98 deletions examples/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
Shows how to use the TEXTEDITING and TEXTINPUT events.
"""
from typing import Tuple, List
import sys
import os

import pygame
import pygame
import pygame.freetype as freetype

# This environment variable is important
# If not added the candidate list will not show
Expand All @@ -37,7 +36,13 @@ class TextInput:
]

def __init__(
self, prompt: str, pos, screen_dimensions, print_event: bool, text_color="white"
self,
prompt: str,
pos: Tuple[int, int],
screen_dimensions: Tuple[int, int],
print_event: bool,
text_color="white",
fps: int = 50,
) -> None:
self.prompt = prompt
self.print_event = print_event
Expand All @@ -53,124 +58,197 @@ def __init__(
self._ime_text_pos = 0
self._ime_editing_text = ""
self._ime_editing_pos = 0
self.chat_list = []
self.chat = ""

# Freetype
# The font name can be a comma separated list
# of font names to search for.
self.FONT_NAMES = ",".join(str(x) for x in self.FONT_NAMES)
self.font = freetype.SysFont(self.FONT_NAMES, 24)
self.font_small = freetype.SysFont(self.FONT_NAMES, 16)
self.font_names = ",".join(self.FONT_NAMES)
self.font = pygame.font.SysFont(self.font_names, 24)
self.font_height = self.font.get_height()
self.font_small = pygame.font.SysFont(self.font_names, 16)
self.text_color = text_color

self.prompt_surf = self.font.render(self.prompt, True, self.text_color)
self.prompt_rect = self.prompt_surf.get_rect(topleft=self.CHAT_BOX_POS.topleft)

self.fps = fps
self.second_counter = 0

print("Using font: " + self.font.name)

def update(self, events) -> None:
def update(self, events: List[pygame.Event]) -> None:
"""
Updates the text input widget
"""
for event in events:
if event.type == pygame.KEYDOWN:
if self.print_event:
print(event)
self.handle_event(event)

self.second_counter += 1

if self.second_counter >= self.fps:
self.second_counter = 0

# Check if input fits in chat box
input_size = self.font.size(self._get_ime_text())
while input_size[0] > self.CHAT_BOX_POS.w - self.prompt_rect.w:
if self._ime_editing_text:
# Don't block.
break
self._ime_text = self._ime_text[:-1]
input_size = self.font.size(self._get_ime_text())

def _clamp_to_text_range(self, num: int):
return min(len(self._ime_text), max(0, num))

def move_cursor_by(self, by: int):
self._ime_text_pos = self._clamp_to_text_range(self._ime_text_pos + by)

def replace_chars(
self,
remove_count: int = 0,
to_insert: str = "",
text_after_cursor: bool = False,
):
"""
Removes given number of characters from the cursor location
and adds an optional string there, then adjusts the cursor location.
"""
loc = self._clamp_to_text_range(remove_count + self._ime_text_pos)

if self._ime_editing:
if len(self._ime_editing_text) == 0:
self._ime_editing = False
continue
if remove_count < 0:
self._ime_text = (
self._ime_text[0:loc] + to_insert + self._ime_text[self._ime_text_pos :]
)

if text_after_cursor:
self.move_cursor_by(remove_count)
else:
self.move_cursor_by(remove_count + len(to_insert))
else:
self._ime_text = (
self._ime_text[0 : self._ime_text_pos]
+ to_insert
+ self._ime_text[loc:]
)

# Don't move cursor if not inserting text
# after removing the characters in front of the cursor
if not text_after_cursor:
self.move_cursor_by(len(to_insert))

def handle_event(self, event: pygame.Event):
"""
Handle an event
"""
if self.print_event:
print(event)

if event.type == pygame.KEYDOWN:
if self._ime_editing:
if len(self._ime_editing_text) == 0:
self._ime_editing = False
return

if event.key == pygame.K_BACKSPACE:
self.replace_chars(-1)

elif event.key == pygame.K_DELETE:
self.replace_chars(1)

elif event.key == pygame.K_LEFT:
self.move_cursor_by(-1)

elif event.key == pygame.K_RIGHT:
self.move_cursor_by(1)

# Handle ENTER key
elif event.key in (pygame.K_RETURN, pygame.K_KP_ENTER):
# Block if we have no text to append
if len(self._ime_text) == 0:
return

# Add to chat log
self.chat += self._ime_text + "\n"

if event.key == pygame.K_BACKSPACE:
if len(self._ime_text) > 0 and self._ime_text_pos > 0:
self._ime_text = (
self._ime_text[0 : self._ime_text_pos - 1]
+ self._ime_text[self._ime_text_pos :]
)
self._ime_text_pos = max(0, self._ime_text_pos - 1)

elif event.key == pygame.K_DELETE:
self._ime_text = (
self._ime_text[0 : self._ime_text_pos]
+ self._ime_text[self._ime_text_pos + 1 :]
)
elif event.key == pygame.K_LEFT:
self._ime_text_pos = max(0, self._ime_text_pos - 1)
elif event.key == pygame.K_RIGHT:
self._ime_text_pos = min(
len(self._ime_text), self._ime_text_pos + 1
)
# Handle ENTER key
elif event.key in [pygame.K_RETURN, pygame.K_KP_ENTER]:
# Block if we have no text to append
if len(self._ime_text) == 0:
continue

# Append chat list
self.chat_list.append(self._ime_text)
if len(self.chat_list) > self.CHAT_LIST_MAXSIZE:
self.chat_list.pop(0)
self._ime_text = ""
self._ime_text_pos = 0

elif event.type == pygame.TEXTEDITING:
if self.print_event:
print(event)
self._ime_editing = True
self._ime_editing_text = event.text
self._ime_editing_pos = event.start

elif event.type == pygame.TEXTINPUT:
if self.print_event:
print(event)
self._ime_editing = False
self._ime_editing_text = ""
self._ime_text = (
self._ime_text[0 : self._ime_text_pos]
+ event.text
+ self._ime_text[self._ime_text_pos :]
)
self._ime_text_pos += len(event.text)
chat_lines = self.chat.split("\n")
if len(chat_lines) > self.CHAT_LIST_MAXSIZE:
chat_lines.pop(0)
self.chat = "\n".join(chat_lines)

self._ime_text = ""
self._ime_text_pos = 0

elif event.type == pygame.TEXTEDITING:
self._ime_editing = True
self._ime_editing_text = event.text
self._ime_editing_pos = event.start

elif event.type == pygame.TEXTINPUT:
self._ime_editing = False
self._ime_editing_text = ""
self.replace_chars(to_insert=event.text)

def _get_ime_text(self):
"""
Returns text that is currently in input.
"""
if self._ime_editing_text:
return (
f"{self._ime_text[0: self._ime_text_pos]}"
f"[{self._ime_editing_text}]"
f"{self._ime_text[self._ime_text_pos:]}"
)
return (
f"{self._ime_text[0: self._ime_text_pos]}"
f"{self._ime_text[self._ime_text_pos:]}"
)

def draw(self, screen: pygame.Surface) -> None:
"""
Draws the text input widget onto the provided surface
"""

# Chat List updates
chat_height = self.CHAT_LIST_POS.height / self.CHAT_LIST_MAXSIZE
for i, chat in enumerate(self.chat_list):
self.font_small.render_to(
screen,
(self.CHAT_LIST_POS.x, self.CHAT_LIST_POS.y + i * chat_height),
chat,
self.text_color,
)
chat_list_surf = self.font_small.render(
self.chat, True, self.text_color, wraplength=self.CHAT_LIST_POS.width
)

screen.blit(chat_list_surf, self.CHAT_LIST_POS)

# Chat box updates
start_pos = self.CHAT_BOX_POS.copy()
ime_text_l = self.prompt + self._ime_text[0 : self._ime_text_pos]
ime_text_m = (
self._ime_editing_text[0 : self._ime_editing_pos]
+ "|"
+ self._ime_editing_text[self._ime_editing_pos :]
)
ime_text_r = self._ime_text[self._ime_text_pos :]
cursor_loc = self._ime_text_pos + self._ime_editing_pos
ime_text = self._get_ime_text()

rect_text_l = self.font.render_to(
screen, start_pos, ime_text_l, self.text_color
)
start_pos.x += rect_text_l.width

# Editing texts should be underlined
rect_text_m = self.font.render_to(
screen,
start_pos,
ime_text_m,
self.text_color,
None,
freetype.STYLE_UNDERLINE,
text_surf = self.font.render(
ime_text, True, self.text_color, wraplength=self.CHAT_BOX_POS.width
)
start_pos.x += rect_text_m.width
self.font.render_to(screen, start_pos, ime_text_r, self.text_color)

text_rect = text_surf.get_rect(topleft=self.prompt_rect.topright)
screen.blit(self.prompt_surf, self.prompt_rect)
screen.blit(text_surf, text_rect)

# Show blinking cursor, blink twice a second.
if self.second_counter * 2 < self.fps:
# Characters can have different widths,
# so calculating the correct location for the cursor is required.
metrics = self.font.metrics(ime_text)
x_location = 0

index = 0
for metric in metrics:
if metric is None:
continue

_, _, _, _, x_advance = metric

if index >= cursor_loc:
break
x_location += x_advance
index += 1

cursor_rect = pygame.Rect(
x_location + text_rect.x, text_rect.y, 2, self.font_height
)
pygame.draw.rect(screen, self.text_color, cursor_rect)


class Game:
Expand Down Expand Up @@ -201,6 +279,7 @@ def __init__(self, caption: str) -> None:
screen_dimensions=(self.SCREEN_WIDTH, self.SCREEN_HEIGHT),
print_event=self.print_event,
text_color="green",
fps=self.FPS,
)

def main_loop(self) -> None:
Expand Down
16 changes: 8 additions & 8 deletions examples/vgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
method of importing numpy and pygame.surfarray. This method
will fail 'gracefully' if it is not available.
I've tried mixing in a lot of comments where the code might
not be self explanatory, nonetheless it may still seem a bit
not be self-explanatory, nonetheless it may still seem a bit
strange. Learning to use numpy for images like this takes a
bit of learning, but the payoff is extremely fast image
manipulation in python.
Expand Down Expand Up @@ -40,7 +40,7 @@


def stopwatch(message=None):
"simple routine to time python code"
"""simple routine to time python code"""
global timer
if not message:
timer = pygame.time.get_ticks()
Expand All @@ -51,8 +51,8 @@ def stopwatch(message=None):
timer = now


def VertGradientColumn(surf, topcolor, bottomcolor):
"creates a new 3d vertical gradient array"
def vert_gradient_column(surf, topcolor, bottomcolor):
"""creates a new 3d vertical gradient array"""
topcolor = np.array(topcolor, copy=False)
bottomcolor = np.array(bottomcolor, copy=False)
diff = bottomcolor - topcolor
Expand All @@ -68,11 +68,11 @@ def VertGradientColumn(surf, topcolor, bottomcolor):
return pygame.surfarray.map_array(surf, column)


def DisplayGradient(surf):
"choose random colors and show them"
def display_gradient(surf):
"""choose random colors and show them"""
stopwatch()
colors = np_random.randint(0, 255, (2, 3))
column = VertGradientColumn(surf, colors[0], colors[1])
column = vert_gradient_column(surf, colors[0], colors[1])
pygame.surfarray.blit_array(surf, column)
pygame.display.flip()
stopwatch("Gradient:")
Expand All @@ -95,7 +95,7 @@ def main():
if event.type in (pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN):
break
elif event.type == TIMER_EVENT:
DisplayGradient(screen)
display_gradient(screen)

pygame.quit()

Expand Down

0 comments on commit b560863

Please sign in to comment.