1
+ # SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
1
5
import sys
2
6
import os
3
7
import time
4
8
from enum import Enum
5
9
import pygame
6
10
7
11
# 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"
13
17
14
18
# 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/"
17
21
18
22
# Font Path, Size
19
23
TITLE_FONT = (FONTS_PATH + "lucida_black.ttf" , 48 )
29
33
PARAGRAPH_DELAY = 2
30
34
31
35
# 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
36
40
37
41
# 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
42
46
43
47
# 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
48
52
49
53
50
54
# Whitespace Settings in Pixels
55
59
EXTRA_LINE_SPACING = 0
56
60
PARAGRAPH_SPACING = 30
57
61
62
+
58
63
class Position (Enum ):
59
64
TOP = 0
60
65
CENTER = 1
61
66
BOTTOM = 2
62
67
LEFT = 3
63
68
RIGHT = 4
64
69
70
+
65
71
class Button :
66
72
def __init__ (self , x , y , image , action ):
67
73
self .x = x
@@ -72,7 +78,9 @@ def __init__(self, x, y, image, action):
72
78
self ._height = self .image .get_height ()
73
79
74
80
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
+ )
76
84
77
85
def is_pressed (self ):
78
86
pass
@@ -85,6 +93,7 @@ def width(self):
85
93
def height (self ):
86
94
return self ._height
87
95
96
+
88
97
class Textarea :
89
98
def __init__ (self , x , y , width , height ):
90
99
self .x = x
@@ -94,10 +103,8 @@ def __init__(self, x, y, width, height):
94
103
95
104
@property
96
105
def size (self ):
97
- return {
98
- "width" : self .width ,
99
- "height" : self .height
100
- }
106
+ return {"width" : self .width , "height" : self .height }
107
+
101
108
102
109
class Book :
103
110
def __init__ (self , rotation = 0 ):
@@ -113,14 +120,17 @@ def __init__(self, rotation=0):
113
120
self .height = 0
114
121
self .back_button = None
115
122
self .next_button = None
123
+ self .textarea = None
124
+ self .screen = None
125
+ self .cursor = None
116
126
117
127
def init (self ):
118
128
# Output to the LCD instead of the console
119
- os .putenv (' DISPLAY' , ':0' )
129
+ os .putenv (" DISPLAY" , ":0" )
120
130
121
131
# Initialize the display
122
132
pygame .init ()
123
- self .screen = pygame .display .set_mode ((0 ,0 ), pygame .FULLSCREEN )
133
+ self .screen = pygame .display .set_mode ((0 , 0 ), pygame .FULLSCREEN )
124
134
self .width = self .screen .get_height ()
125
135
self .height = self .screen .get_width ()
126
136
@@ -136,37 +146,40 @@ def init(self):
136
146
# Add buttons
137
147
back_button_image = pygame .image .load (IMAGES_PATH + BUTTON_BACK_IMAGE )
138
148
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
+ )
141
157
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
146
159
)
147
160
self .next_button = Button (
148
161
self .width - button_spacing - next_button_image .get_width (),
149
162
button_ypos ,
150
163
next_button_image ,
151
- self .next_page
164
+ self .next_page ,
152
165
)
153
166
154
167
# Add Text Area
155
168
self .textarea = Textarea (
156
169
PAGE_SIDE_MARGIN ,
157
170
PAGE_TOP_MARGIN ,
158
171
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 ,
160
173
)
161
174
162
175
pygame .mouse .set_visible (False )
163
- self .screen .fill ((255 ,255 ,255 ))
176
+ self .screen .fill ((255 , 255 , 255 ))
164
177
165
178
def handle_events (self ):
166
179
for event in pygame .event .get ():
167
180
if event .type == pygame .QUIT :
168
181
raise SystemExit
169
- elif event .type == pygame .MOUSEBUTTONDOWN :
182
+ if event .type == pygame .MOUSEBUTTONDOWN :
170
183
if event .button == 1 :
171
184
# If clicked in text area and book is still rendering, skip to the end
172
185
print (f"Left mouse button pressed at { event .pos } " )
@@ -177,35 +190,37 @@ def handle_events(self):
177
190
178
191
def add_page (self , paragraph = 0 , word = 0 ):
179
192
# 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
+ )
184
199
185
200
def load_image (self , name , filename ):
186
201
try :
187
202
image = pygame .image .load (IMAGES_PATH + filename )
188
203
self .images [name ] = image
189
204
except pygame .error :
190
- return None
205
+ pass
191
206
192
207
def load_font (self , name , details ):
193
208
self .fonts [name ] = pygame .font .Font (details [0 ], details [1 ])
194
209
195
- def get_position (self , object , x , y ):
210
+ def get_position (self , obj , x , y ):
196
211
if x == Position .CENTER :
197
- x = (self .width - object .get_width ()) // 2
212
+ x = (self .width - obj .get_width ()) // 2
198
213
elif x == Position .RIGHT :
199
- x = self .width - object .get_width ()
214
+ x = self .width - obj .get_width ()
200
215
elif x == Position .LEFT :
201
216
x = 0
202
217
elif not isinstance (x , int ):
203
218
raise ValueError ("Invalid x position" )
204
219
205
220
if y == Position .CENTER :
206
- y = (self .height - object .get_height ()) // 2
221
+ y = (self .height - obj .get_height ()) // 2
207
222
elif y == Position .BOTTOM :
208
- y = self .height - object .get_height ()
223
+ y = self .height - obj .get_height ()
209
224
elif y == Position .TOP :
210
225
y = 0
211
226
elif not isinstance (y , int ):
@@ -225,22 +240,21 @@ def display_image(self, image, x=Position.CENTER, y=Position.CENTER, surface=Non
225
240
surface .blit (buffer , (0 , 0 ))
226
241
227
242
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
-
230
243
self .display_image (self .images ["background" ], Position .CENTER , Position .CENTER )
231
244
pygame .display .update ()
232
245
233
246
# Use a cursor to keep track of where we are on the page
234
247
# 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 }
239
249
240
250
# Display the title
241
251
if self .page == 0 :
242
252
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
+ )
244
258
pygame .display .update ()
245
259
self .cursor ["y" ] += title .get_height () + PARAGRAPH_SPACING
246
260
time .sleep (PARAGRAPH_DELAY )
@@ -249,20 +263,23 @@ def display_current_page(self):
249
263
250
264
# Display the navigation buttons
251
265
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
+ )
253
269
254
270
# 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
+ )
256
274
pygame .display .update ()
257
275
258
276
def render_character (self , character ):
259
277
return self .fonts ["text" ].render (character , True , (0 , 0 , 0 ))
260
278
261
279
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
263
282
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
266
283
paragraph_number = self .pages [self .page ]["paragraph" ]
267
284
word_number = self .pages [self .page ]["word" ]
268
285
@@ -272,19 +289,31 @@ def display_page_text(self):
272
289
while word_number < len (paragraph ):
273
290
word = paragraph [word_number ]
274
291
# 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
+ ):
276
296
# If not, move to the next line
277
297
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
+ )
279
301
# 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
+ ):
281
306
self .add_page (paragraph_number , word_number )
282
307
return
283
308
284
309
# Display the word one character at a time
285
310
for character in word :
286
311
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
+ )
288
317
pygame .display .update ()
289
318
self .cursor ["x" ] += character_surface .get_width () + 1
290
319
if character != " " :
@@ -293,7 +322,8 @@ def display_page_text(self):
293
322
# Advance the cursor by a spaces width
294
323
self .cursor ["x" ] += self .render_character (" " ).get_width () + 1
295
324
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 "..."
297
327
if word [- 1 :] in ["." , "!" , "?" ]:
298
328
time .sleep (SENTENCE_DELAY )
299
329
else :
@@ -308,16 +338,19 @@ def display_page_text(self):
308
338
paragraph_number += 1
309
339
310
340
# 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
+ ):
312
345
self .add_page (paragraph_number , word_number )
313
346
return
314
347
315
348
def create_transparent_buffer (self , size ):
316
349
if isinstance (size , (tuple , list )):
317
350
(width , height ) = size
318
351
elif isinstance (size , dict ):
319
- width = size [' width' ]
320
- height = size [' height' ]
352
+ width = size [" width" ]
353
+ height = size [" height" ]
321
354
buffer = pygame .Surface ((width , height ), pygame .SRCALPHA , 32 )
322
355
buffer = buffer .convert_alpha ()
323
356
return buffer
@@ -331,7 +364,9 @@ def render_title(self):
331
364
text_height = 0
332
365
for line in lines :
333
366
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
+ )
335
370
text_height += text .get_height ()
336
371
337
372
new_buffer = self .create_transparent_buffer ((self .textarea .width , text_height ))
@@ -378,4 +413,4 @@ def parse_story(self, story):
378
413
self .add_page ()
379
414
380
415
# save settings
381
- # load settings
416
+ # load settings
0 commit comments