Skip to content

Commit 040a25e

Browse files
committed
SMS Scroller Code update
adding Messageboard folder to lib
1 parent 32467f8 commit 040a25e

11 files changed

Lines changed: 1243 additions & 0 deletions

File tree

149 KB
Binary file not shown.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
import bitmaptools
6+
import displayio
7+
import adafruit_imageload
8+
from .doublebuffer import DoubleBuffer
9+
from .message import Message
10+
11+
12+
class MessageBoard:
13+
def __init__(self, matrix):
14+
self.fonts = {}
15+
self.display = matrix.display
16+
self._buffer_width = self.display.width * 2
17+
self._buffer_height = self.display.height * 2
18+
self._dbl_buf = DoubleBuffer(
19+
self.display, self._buffer_width, self._buffer_height
20+
)
21+
self._background = None
22+
self.set_background() # Set to black
23+
self._position = (0, 0)
24+
25+
def set_background(self, file_or_color=0x000000):
26+
"""The background image to a bitmap file."""
27+
if isinstance(file_or_color, str): # its a filenme:
28+
background, bg_shader = adafruit_imageload.load(file_or_color)
29+
self._dbl_buf.shader = bg_shader
30+
self._background = background
31+
elif isinstance(file_or_color, int):
32+
# Make a background color fill
33+
bg_shader = displayio.ColorConverter(
34+
input_colorspace=displayio.Colorspace.RGB565
35+
)
36+
background = displayio.Bitmap(
37+
self.display.width, self.display.height, 65535
38+
)
39+
background.fill(displayio.ColorConverter().convert(file_or_color))
40+
self._dbl_buf.shader = bg_shader
41+
self._background = background
42+
else:
43+
raise RuntimeError("Unknown type of background")
44+
45+
def animate(self, message, animation_class, animation_function, **kwargs):
46+
anim_class = __import__(
47+
f"{self.__module__}.animations.{animation_class.lower()}"
48+
)
49+
anim_class = getattr(anim_class, "animations")
50+
anim_class = getattr(anim_class, animation_class.lower())
51+
anim_class = getattr(anim_class, animation_class)
52+
animation = anim_class(
53+
self.display, self._draw, self._position
54+
) # Instantiate the class
55+
# Call the animation function and pass kwargs along with the message (positional)
56+
anim_func = getattr(animation, animation_function)
57+
anim_func(message, **kwargs)
58+
59+
def set_message_position(self, x, y):
60+
"""Set the position of the message on the display"""
61+
self._position = (x, y)
62+
63+
def _draw(
64+
self,
65+
image,
66+
x,
67+
y,
68+
opacity=None,
69+
mask_color=0xFF00FF,
70+
blendmode=bitmaptools.BlendMode.Normal,
71+
post_draw_position=None,
72+
):
73+
"""Draws a message to the buffer taking its current settings into account.
74+
It also sets the current position and performs a swap.
75+
"""
76+
self._position = (x, y)
77+
buffer_x_offset = self._buffer_width - self.display.width
78+
buffer_y_offset = self._buffer_height - self.display.height
79+
80+
# Image can be a message in which case its properties will be used
81+
if isinstance(image, Message):
82+
if opacity is None:
83+
opacity = image.opacity
84+
mask_color = image.mask_color
85+
blendmode = image.blendmode
86+
image = image.buffer
87+
if opacity is None:
88+
opacity = 1.0
89+
90+
if mask_color > 65535:
91+
mask_color = displayio.ColorConverter().convert(mask_color)
92+
93+
# Blit the background
94+
bitmaptools.blit(
95+
self._dbl_buf.active_buffer,
96+
self._background,
97+
buffer_x_offset,
98+
buffer_y_offset,
99+
)
100+
101+
# If the image is wider than the display buffer, we need to shrink it
102+
while x + buffer_x_offset < 0:
103+
new_image = displayio.Bitmap(
104+
image.width - self.display.width, image.height, 65535
105+
)
106+
bitmaptools.blit(
107+
new_image,
108+
image,
109+
0,
110+
0,
111+
x1=self.display.width,
112+
y1=0,
113+
x2=image.width,
114+
y2=image.height,
115+
)
116+
x += self.display.width
117+
self._position = (x, y) # Update the stored position
118+
image = new_image
119+
120+
# If the image is taller than the display buffer, we need to shrink it
121+
while y + buffer_y_offset < 0:
122+
new_image = displayio.Bitmap(
123+
image.width, image.height - self.display.height, 65535
124+
)
125+
bitmaptools.blit(
126+
new_image,
127+
image,
128+
0,
129+
0,
130+
x1=0,
131+
y1=self.display.height,
132+
x2=image.width,
133+
y2=image.height,
134+
)
135+
y += self.display.height
136+
self._position = (x, y) # Update the stored position
137+
image = new_image
138+
139+
# Clear the foreground buffer
140+
foreground_buffer = displayio.Bitmap(
141+
self._buffer_width, self._buffer_height, 65535
142+
)
143+
foreground_buffer.fill(mask_color)
144+
145+
bitmaptools.blit(
146+
foreground_buffer, image, x + buffer_x_offset, y + buffer_y_offset
147+
)
148+
149+
# Blend the foreground buffer into the main buffer
150+
bitmaptools.alphablend(
151+
self._dbl_buf.active_buffer,
152+
self._dbl_buf.active_buffer,
153+
foreground_buffer,
154+
displayio.Colorspace.RGB565,
155+
1.0,
156+
opacity,
157+
blendmode=blendmode,
158+
skip_source2_index=mask_color,
159+
)
160+
self._dbl_buf.show()
161+
162+
# Allow for an override of the position after drawing (needed for split effects)
163+
if post_draw_position is not None and isinstance(post_draw_position, tuple):
164+
self._position = post_draw_position
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
import time
6+
7+
8+
class Animation:
9+
def __init__(self, display, draw_callback, starting_position=(0, 0)):
10+
self._display = display
11+
self._position = starting_position
12+
self._draw = draw_callback
13+
14+
@staticmethod
15+
def _wait(start_time, duration):
16+
"""Uses time.monotonic() to wait from the start time for a specified duration"""
17+
while time.monotonic() < (start_time + duration):
18+
pass
19+
return time.monotonic()
20+
21+
def _get_centered_position(self, message):
22+
return int(self._display.width / 2 - message.buffer.width / 2), int(
23+
self._display.height / 2 - message.buffer.height / 2
24+
)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
import time
6+
import displayio
7+
import bitmaptools
8+
from . import Animation
9+
10+
11+
class Loop(Animation):
12+
def _create_loop_image(self, image, x_offset, y_offset, mask_color):
13+
"""Attach a copy of an image by a certain offset so it can be looped."""
14+
if 0 < x_offset < self._display.width:
15+
x_offset = self._display.width
16+
if 0 < y_offset < self._display.height:
17+
y_offset = self._display.height
18+
19+
loop_image = displayio.Bitmap(
20+
image.width + x_offset, image.height + y_offset, 65535
21+
)
22+
loop_image.fill(mask_color)
23+
bitmaptools.blit(loop_image, image, 0, 0)
24+
bitmaptools.blit(loop_image, image, x_offset, y_offset)
25+
26+
return loop_image
27+
28+
def left(self, message, duration=1, count=1):
29+
"""Loop a message towards the left side of the display over a certain period of time by a
30+
certain number of times. The message will re-enter from the right and end up back a the
31+
starting position.
32+
33+
:param message: The message to animate.
34+
:param float count: (optional) The number of times to loop. (default=1)
35+
:param float duration: (optional) The period of time to perform the animation
36+
over. (default=1)
37+
:type message: Message
38+
"""
39+
current_x, current_y = self._position
40+
distance = max(message.buffer.width, self._display.width)
41+
loop_image = self._create_loop_image(
42+
message.buffer, distance, 0, message.mask_color
43+
)
44+
for _ in range(count):
45+
for _ in range(distance):
46+
start_time = time.monotonic()
47+
current_x -= 1
48+
if current_x < 0 - message.buffer.width:
49+
current_x += distance
50+
self._draw(
51+
loop_image,
52+
current_x,
53+
current_y,
54+
message.opacity,
55+
)
56+
self._wait(start_time, duration / distance / count)
57+
58+
def right(self, message, duration=1, count=1):
59+
"""Loop a message towards the right side of the display over a certain period of time by a
60+
certain number of times. The message will re-enter from the left and end up back a the
61+
starting position.
62+
63+
:param message: The message to animate.
64+
:param float count: (optional) The number of times to loop. (default=1)
65+
:param float duration: (optional) The period of time to perform the animation
66+
over. (default=1)
67+
:type message: Message
68+
"""
69+
current_x, current_y = self._position
70+
distance = max(message.buffer.width, self._display.width)
71+
loop_image = self._create_loop_image(
72+
message.buffer, distance, 0, message.mask_color
73+
)
74+
for _ in range(count):
75+
for _ in range(distance):
76+
start_time = time.monotonic()
77+
current_x += 1
78+
if current_x > 0:
79+
current_x -= distance
80+
self._draw(
81+
loop_image,
82+
current_x,
83+
current_y,
84+
message.opacity,
85+
)
86+
self._wait(start_time, duration / distance / count)
87+
88+
def up(self, message, duration=0.5, count=1):
89+
"""Loop a message towards the top side of the display over a certain period of time by a
90+
certain number of times. The message will re-enter from the bottom and end up back a the
91+
starting position.
92+
93+
:param message: The message to animate.
94+
:param float count: (optional) The number of times to loop. (default=1)
95+
:param float duration: (optional) The period of time to perform the animation
96+
over. (default=1)
97+
:type message: Message
98+
"""
99+
current_x, current_y = self._position
100+
distance = max(message.buffer.height, self._display.height)
101+
loop_image = self._create_loop_image(
102+
message.buffer, 0, distance, message.mask_color
103+
)
104+
for _ in range(count):
105+
for _ in range(distance):
106+
start_time = time.monotonic()
107+
current_y -= 1
108+
if current_y < 0 - message.buffer.height:
109+
current_y += distance
110+
self._draw(
111+
loop_image,
112+
current_x,
113+
current_y,
114+
message.opacity,
115+
)
116+
self._wait(start_time, duration / distance / count)
117+
118+
def down(self, message, duration=0.5, count=1):
119+
"""Loop a message towards the bottom side of the display over a certain period of time by a
120+
certain number of times. The message will re-enter from the top and end up back a the
121+
starting position.
122+
123+
:param message: The message to animate.
124+
:param float count: (optional) The number of times to loop. (default=1)
125+
:param float duration: (optional) The period of time to perform the animation
126+
over. (default=1)
127+
:type message: Message
128+
"""
129+
current_x, current_y = self._position
130+
distance = max(message.buffer.height, self._display.height)
131+
loop_image = self._create_loop_image(
132+
message.buffer, 0, distance, message.mask_color
133+
)
134+
for _ in range(count):
135+
for _ in range(distance):
136+
start_time = time.monotonic()
137+
current_y += 1
138+
if current_y > 0:
139+
current_y -= distance
140+
self._draw(
141+
loop_image,
142+
current_x,
143+
current_y,
144+
message.opacity,
145+
)
146+
self._wait(start_time, duration / distance / count)

0 commit comments

Comments
 (0)