1+ # SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2+ #
3+ # SPDX-License-Identifier: MIT
4+
15import sys
26import os
37import time
48from enum import Enum
59import pygame
610
711# Image Names
8- WELCOME_IMAGE = ' welcome.png'
9- BACKGROUND_IMAGE = ' paper_background.png'
10- LOADING_IMAGE = ' loading.png'
11- BUTTON_BACK_IMAGE = ' button_back.png'
12- BUTTON_NEXT_IMAGE = ' button_next.png'
12+ WELCOME_IMAGE = " welcome.png"
13+ BACKGROUND_IMAGE = " paper_background.png"
14+ LOADING_IMAGE = " loading.png"
15+ BUTTON_BACK_IMAGE = " button_back.png"
16+ BUTTON_NEXT_IMAGE = " button_next.png"
1317
1418# Asset Paths
15- IMAGES_PATH = os .path .dirname (sys .argv [0 ]) + ' images/'
16- FONTS_PATH = os .path .dirname (sys .argv [0 ]) + ' fonts/'
19+ IMAGES_PATH = os .path .dirname (sys .argv [0 ]) + " images/"
20+ FONTS_PATH = os .path .dirname (sys .argv [0 ]) + " fonts/"
1721
1822# Font Path, Size
1923TITLE_FONT = (FONTS_PATH + "lucida_black.ttf" , 48 )
2933PARAGRAPH_DELAY = 2
3034
3135# Letter by Letter
32- #CHARACTER_DELAY = 0.1
33- #WORD_DELAY = 0
34- #SENTENCE_DELAY = 0
35- #PARAGRAPH_DELAY = 0
36+ # CHARACTER_DELAY = 0.1
37+ # WORD_DELAY = 0
38+ # SENTENCE_DELAY = 0
39+ # PARAGRAPH_DELAY = 0
3640
3741# Word by Word
38- #CHARACTER_DELAY = 0
39- #WORD_DELAY = 0.3
40- #SENTENCE_DELAY = 0.5
41- #PARAGRAPH_DELAY = 0
42+ # CHARACTER_DELAY = 0
43+ # WORD_DELAY = 0.3
44+ # SENTENCE_DELAY = 0.5
45+ # PARAGRAPH_DELAY = 0
4246
4347# No Delays
44- #CHARACTER_DELAY = 0
45- #WORD_DELAY = 0
46- #SENTENCE_DELAY = 0
47- #PARAGRAPH_DELAY = 0
48+ # CHARACTER_DELAY = 0
49+ # WORD_DELAY = 0
50+ # SENTENCE_DELAY = 0
51+ # PARAGRAPH_DELAY = 0
4852
4953
5054# Whitespace Settings in Pixels
5559EXTRA_LINE_SPACING = 0
5660PARAGRAPH_SPACING = 30
5761
62+
5863class Position (Enum ):
5964 TOP = 0
6065 CENTER = 1
6166 BOTTOM = 2
6267 LEFT = 3
6368 RIGHT = 4
6469
70+
6571class Button :
6672 def __init__ (self , x , y , image , action ):
6773 self .x = x
@@ -72,7 +78,9 @@ def __init__(self, x, y, image, action):
7278 self ._height = self .image .get_height ()
7379
7480 def is_in_bounds (self , x , y ):
75- return self .x <= x <= self .x + self .width and self .y <= y <= self .y + self .height
81+ return (
82+ self .x <= x <= self .x + self .width and self .y <= y <= self .y + self .height
83+ )
7684
7785 def is_pressed (self ):
7886 pass
@@ -85,6 +93,7 @@ def width(self):
8593 def height (self ):
8694 return self ._height
8795
96+
8897class Textarea :
8998 def __init__ (self , x , y , width , height ):
9099 self .x = x
@@ -94,10 +103,8 @@ def __init__(self, x, y, width, height):
94103
95104 @property
96105 def size (self ):
97- return {
98- "width" : self .width ,
99- "height" : self .height
100- }
106+ return {"width" : self .width , "height" : self .height }
107+
101108
102109class Book :
103110 def __init__ (self , rotation = 0 ):
@@ -113,14 +120,17 @@ def __init__(self, rotation=0):
113120 self .height = 0
114121 self .back_button = None
115122 self .next_button = None
123+ self .textarea = None
124+ self .screen = None
125+ self .cursor = None
116126
117127 def init (self ):
118128 # Output to the LCD instead of the console
119- os .putenv (' DISPLAY' , ':0' )
129+ os .putenv (" DISPLAY" , ":0" )
120130
121131 # Initialize the display
122132 pygame .init ()
123- self .screen = pygame .display .set_mode ((0 ,0 ), pygame .FULLSCREEN )
133+ self .screen = pygame .display .set_mode ((0 , 0 ), pygame .FULLSCREEN )
124134 self .width = self .screen .get_height ()
125135 self .height = self .screen .get_width ()
126136
@@ -136,37 +146,40 @@ def init(self):
136146 # Add buttons
137147 back_button_image = pygame .image .load (IMAGES_PATH + BUTTON_BACK_IMAGE )
138148 next_button_image = pygame .image .load (IMAGES_PATH + BUTTON_NEXT_IMAGE )
139- button_spacing = (self .width - (back_button_image .get_width () + next_button_image .get_width ())) // 3
140- button_ypos = self .height - PAGE_NAV_HEIGHT + (PAGE_NAV_HEIGHT - next_button_image .get_height ()) // 2
149+ button_spacing = (
150+ self .width - (back_button_image .get_width () + next_button_image .get_width ())
151+ ) // 3
152+ button_ypos = (
153+ self .height
154+ - PAGE_NAV_HEIGHT
155+ + (PAGE_NAV_HEIGHT - next_button_image .get_height ()) // 2
156+ )
141157 self .back_button = Button (
142- button_spacing ,
143- button_ypos ,
144- back_button_image ,
145- self .previous_page
158+ button_spacing , button_ypos , back_button_image , self .previous_page
146159 )
147160 self .next_button = Button (
148161 self .width - button_spacing - next_button_image .get_width (),
149162 button_ypos ,
150163 next_button_image ,
151- self .next_page
164+ self .next_page ,
152165 )
153166
154167 # Add Text Area
155168 self .textarea = Textarea (
156169 PAGE_SIDE_MARGIN ,
157170 PAGE_TOP_MARGIN ,
158171 self .width - PAGE_SIDE_MARGIN * 2 ,
159- self .height - PAGE_NAV_HEIGHT - PAGE_TOP_MARGIN - PAGE_BOTTOM_MARGIN
172+ self .height - PAGE_NAV_HEIGHT - PAGE_TOP_MARGIN - PAGE_BOTTOM_MARGIN ,
160173 )
161174
162175 pygame .mouse .set_visible (False )
163- self .screen .fill ((255 ,255 ,255 ))
176+ self .screen .fill ((255 , 255 , 255 ))
164177
165178 def handle_events (self ):
166179 for event in pygame .event .get ():
167180 if event .type == pygame .QUIT :
168181 raise SystemExit
169- elif event .type == pygame .MOUSEBUTTONDOWN :
182+ if event .type == pygame .MOUSEBUTTONDOWN :
170183 if event .button == 1 :
171184 # If clicked in text area and book is still rendering, skip to the end
172185 print (f"Left mouse button pressed at { event .pos } " )
@@ -177,35 +190,37 @@ def handle_events(self):
177190
178191 def add_page (self , paragraph = 0 , word = 0 ):
179192 # Add rendered page information to make flipping between them easier
180- self .pages .append ({
181- "paragraph" : paragraph ,
182- "word" : word ,
183- })
193+ self .pages .append (
194+ {
195+ "paragraph" : paragraph ,
196+ "word" : word ,
197+ }
198+ )
184199
185200 def load_image (self , name , filename ):
186201 try :
187202 image = pygame .image .load (IMAGES_PATH + filename )
188203 self .images [name ] = image
189204 except pygame .error :
190- return None
205+ pass
191206
192207 def load_font (self , name , details ):
193208 self .fonts [name ] = pygame .font .Font (details [0 ], details [1 ])
194209
195- def get_position (self , object , x , y ):
210+ def get_position (self , obj , x , y ):
196211 if x == Position .CENTER :
197- x = (self .width - object .get_width ()) // 2
212+ x = (self .width - obj .get_width ()) // 2
198213 elif x == Position .RIGHT :
199- x = self .width - object .get_width ()
214+ x = self .width - obj .get_width ()
200215 elif x == Position .LEFT :
201216 x = 0
202217 elif not isinstance (x , int ):
203218 raise ValueError ("Invalid x position" )
204219
205220 if y == Position .CENTER :
206- y = (self .height - object .get_height ()) // 2
221+ y = (self .height - obj .get_height ()) // 2
207222 elif y == Position .BOTTOM :
208- y = self .height - object .get_height ()
223+ y = self .height - obj .get_height ()
209224 elif y == Position .TOP :
210225 y = 0
211226 elif not isinstance (y , int ):
@@ -225,22 +240,21 @@ def display_image(self, image, x=Position.CENTER, y=Position.CENTER, surface=Non
225240 surface .blit (buffer , (0 , 0 ))
226241
227242 def display_current_page (self ):
228- # This will be easier if we create a surface and just rotate that before rendering it to the screen
229-
230243 self .display_image (self .images ["background" ], Position .CENTER , Position .CENTER )
231244 pygame .display .update ()
232245
233246 # Use a cursor to keep track of where we are on the page
234247 # These values are relative to the text area
235- self .cursor = {
236- "x" : 0 ,
237- "y" : 0
238- }
248+ self .cursor = {"x" : 0 , "y" : 0 }
239249
240250 # Display the title
241251 if self .page == 0 :
242252 title = self .render_title ()
243- self .display_image (title , self .cursor ["x" ] + self .textarea .x , self .cursor ["y" ] + self .textarea .y )
253+ self .display_image (
254+ title ,
255+ self .cursor ["x" ] + self .textarea .x ,
256+ self .cursor ["y" ] + self .textarea .y ,
257+ )
244258 pygame .display .update ()
245259 self .cursor ["y" ] += title .get_height () + PARAGRAPH_SPACING
246260 time .sleep (PARAGRAPH_DELAY )
@@ -249,20 +263,23 @@ def display_current_page(self):
249263
250264 # Display the navigation buttons
251265 if self .page > 0 :
252- self .display_image (self .back_button .image , self .back_button .x , self .back_button .y )
266+ self .display_image (
267+ self .back_button .image , self .back_button .x , self .back_button .y
268+ )
253269
254270 # TODO: If we are on the last page, don't display the next button
255- self .display_image (self .next_button .image , self .next_button .x , self .next_button .y )
271+ self .display_image (
272+ self .next_button .image , self .next_button .x , self .next_button .y
273+ )
256274 pygame .display .update ()
257275
258276 def render_character (self , character ):
259277 return self .fonts ["text" ].render (character , True , (0 , 0 , 0 ))
260278
261279 def display_page_text (self ):
262- # TODO: We need an accurate way to determine when a previous page has already been added so we don't add it again
280+ # TODO: We need an accurate way to determine when a
281+ # previous page has already been added so we don't add it again
263282
264- # Display the paragraphs, one paragraph at a time, one word at a time until we reach the end of the line
265- # then move to the next line. Once we are at the end of the page, stop displaying paragraphs
266283 paragraph_number = self .pages [self .page ]["paragraph" ]
267284 word_number = self .pages [self .page ]["word" ]
268285
@@ -272,19 +289,31 @@ def display_page_text(self):
272289 while word_number < len (paragraph ):
273290 word = paragraph [word_number ]
274291 # Check if there is enough space to display the word
275- if self .cursor ["x" ] + self .fonts ["text" ].size (word )[0 ] > self .textarea .width :
292+ if (
293+ self .cursor ["x" ] + self .fonts ["text" ].size (word )[0 ]
294+ > self .textarea .width
295+ ):
276296 # If not, move to the next line
277297 self .cursor ["x" ] = 0
278- self .cursor ["y" ] += self .fonts ["text" ].get_height () + EXTRA_LINE_SPACING
298+ self .cursor ["y" ] += (
299+ self .fonts ["text" ].get_height () + EXTRA_LINE_SPACING
300+ )
279301 # If we have reached the end of the page, stop displaying paragraphs
280- if self .cursor ["y" ] + self .fonts ["text" ].get_height () > self .textarea .height :
302+ if (
303+ self .cursor ["y" ] + self .fonts ["text" ].get_height ()
304+ > self .textarea .height
305+ ):
281306 self .add_page (paragraph_number , word_number )
282307 return
283308
284309 # Display the word one character at a time
285310 for character in word :
286311 character_surface = self .render_character (character )
287- self .display_image (character_surface , self .cursor ["x" ] + self .textarea .x , self .cursor ["y" ] + self .textarea .y )
312+ self .display_image (
313+ character_surface ,
314+ self .cursor ["x" ] + self .textarea .x ,
315+ self .cursor ["y" ] + self .textarea .y ,
316+ )
288317 pygame .display .update ()
289318 self .cursor ["x" ] += character_surface .get_width () + 1
290319 if character != " " :
@@ -293,7 +322,8 @@ def display_page_text(self):
293322 # Advance the cursor by a spaces width
294323 self .cursor ["x" ] += self .render_character (" " ).get_width () + 1
295324
296- # Look at last character only to avoid long delays on stuff like "!!!" or "?!" or "..."
325+ # Look at last character only to avoid long delays on stuff
326+ # like "!!!" or "?!" or "..."
297327 if word [- 1 :] in ["." , "!" , "?" ]:
298328 time .sleep (SENTENCE_DELAY )
299329 else :
@@ -308,16 +338,19 @@ def display_page_text(self):
308338 paragraph_number += 1
309339
310340 # If we have reached the end of the page, stop displaying paragraphs
311- if self .cursor ["y" ] + self .fonts ["text" ].get_height () > self .textarea .height :
341+ if (
342+ self .cursor ["y" ] + self .fonts ["text" ].get_height ()
343+ > self .textarea .height
344+ ):
312345 self .add_page (paragraph_number , word_number )
313346 return
314347
315348 def create_transparent_buffer (self , size ):
316349 if isinstance (size , (tuple , list )):
317350 (width , height ) = size
318351 elif isinstance (size , dict ):
319- width = size [' width' ]
320- height = size [' height' ]
352+ width = size [" width" ]
353+ height = size [" height" ]
321354 buffer = pygame .Surface ((width , height ), pygame .SRCALPHA , 32 )
322355 buffer = buffer .convert_alpha ()
323356 return buffer
@@ -331,7 +364,9 @@ def render_title(self):
331364 text_height = 0
332365 for line in lines :
333366 text = self .fonts ["title" ].render (line , True , TITLE_COLOR )
334- buffer .blit (text , (buffer .get_width () // 2 - text .get_width () // 2 , text_height ))
367+ buffer .blit (
368+ text , (buffer .get_width () // 2 - text .get_width () // 2 , text_height )
369+ )
335370 text_height += text .get_height ()
336371
337372 new_buffer = self .create_transparent_buffer ((self .textarea .width , text_height ))
@@ -378,4 +413,4 @@ def parse_story(self, story):
378413 self .add_page ()
379414
380415 # save settings
381- # load settings
416+ # load settings
0 commit comments