Skip to content

Commit

Permalink
Added example
Browse files Browse the repository at this point in the history
  • Loading branch information
tsbarnes committed Sep 15, 2021
1 parent 5e97d35 commit 156db23
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 21 deletions.
98 changes: 77 additions & 21 deletions apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,45 @@


class AbstractApp:
"""
Abstract class for apps
"""
framebuffer: Framebuffer = None
image: Image = None
reload_interval: int = 60
reload_wait: int = 0

def __init__(self, fb):
"""
Default app constructor, sets the framebuffer
:param fb: Framebuffer
"""
self.framebuffer = fb
self.blank()
self.reload()

def blank(self):
"""
Reset the image back to the default
:return: None
"""
if settings.BACKGROUND:
image: Image = Image.open(settings.BACKGROUND)
self.image = image.resize(self.framebuffer.size)
else:
self.image = Image.new("RGBA", self.framebuffer.size, settings.BACKGROUND_COLOR)

def text(self, text, position=(5, 5), font_name=None, font_size=20, color=None):
def text(self, text, position=(5, 5), font_name=None, font_size=20, color=None, wrap=False):
"""
Draws text onto the app's image
:param text: string to draw
:param position: tuple representing where to draw the text
:param font_name: filename of font to use, None for default
:param font_size: integer font size to draw
:param color: color of the text
:param wrap: boolean whether to wrap the text
:return: integer number of lines drawn
"""
if not font_name:
font_name = settings.FONT
if not color:
Expand All @@ -39,51 +60,79 @@ def text(self, text, position=(5, 5), font_name=None, font_size=20, color=None):

font: ImageFont = ImageFont.truetype(font_name, font_size)
draw: ImageDraw = ImageDraw.Draw(self.image)
number_of_lines: int = 0
scaled_wrapped_text: str = ''

draw.text(position, text, font=font, fill=color)

def wrapped_text(self, text, position=(5, 5), font_name=None, font_size=20, color=None):
if not font_name:
font_name = settings.FONT
if not color:
color = settings.TEXT_COLOR
if not self.image:
raise ValueError("self.image is None")

font: ImageFont = ImageFont.truetype(font_name, font_size)
draw: ImageDraw = ImageDraw.Draw(self.image)
if wrap:
avg_char_width: int = sum(font.getsize(char)[0] for char in ascii_letters) / len(ascii_letters)
max_char_count: int = int((self.image.size[0] * .95) / avg_char_width)

avg_char_width: int = sum(font.getsize(char)[0] for char in ascii_letters) / len(ascii_letters)
max_char_count: int = int((self.image.size[0] * .95) / avg_char_width)
for line in text.split('\n'):
new_wrapped_text = textwrap.fill(text=line, width=max_char_count) + '\n'
number_of_lines += len(new_wrapped_text.split('\n'))
scaled_wrapped_text += new_wrapped_text
else:
number_of_lines = 1
scaled_wrapped_text = text

number_of_lines: int = 0
scaled_wrapped_text: str = ''
for line in text.split('\n'):
new_wrapped_text = textwrap.fill(text=line, width=max_char_count) + '\n'
number_of_lines += len(new_wrapped_text.split('\n'))
scaled_wrapped_text += new_wrapped_text
draw.text(position, scaled_wrapped_text, font=font, fill=color)

return number_of_lines

def wrapped_text(self, text, position=(5, 5), font_name=None, font_size=20, color=None):
"""
DEPRECATED - use text(wrap=True) instead
Draws text onto the app's image, uses line wrapping
:param text: string to draw
:param position: tuple representing where to draw the text
:param font_name: filename of font to use, None for default
:param font_size: integer font size to draw
:param color: color of the text
:return: integer number of lines drawn
"""
return self.text(text, position, font_name, font_size, color, wrap=True)

def paste_image(self, image, position=(5, 5)):
"""
Draws an image onto the app's image
:param image: Image to use
:param position: tuple position to draw the image
:return: None
"""
if not self.image:
raise ValueError("self.image is None")
self.image.paste(image, position)

def show(self):
"""
Draw's the app's image to the framebuffer. Avoid calling this whenever possible.
:return: None
"""
if not self.image:
logging.error("App '{0}' called 'show' without an image!".format(self.__module__))
self.blank()
self.framebuffer.show(self.image)

def reload(self):
"""
Runs when the app needs to redraw the app image, do your drawing here
:return: None
"""
raise NotImplementedError()

def touch(self, position: tuple):
"""
Called when the screen is tapped
:param position: tuple where the screen was touched
:return: None
"""
logging.debug("Unhandled PiTFT touch event: {}".format(position))

def run_iteration(self):
"""
Main loop function. If you override this, be sure to call super().run_iteration()
:return: None
"""
self.reload_wait += 1
if not self.image or self.reload_wait >= self.reload_interval:
if self.image:
Expand All @@ -94,6 +143,10 @@ def run_iteration(self):


def get_apps():
"""
Get the full list of apps in the apps directory
:return: list of module names for the apps
"""
apps: list = []

path: str = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
Expand All @@ -110,5 +163,8 @@ def get_apps():


if __name__ == '__main__':
"""
If run directly, this module tests the get_apps() function and exits
"""
logging.basicConfig(level=logging.DEBUG)
get_apps()
52 changes: 52 additions & 0 deletions apps/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Example app to show how to make them"""
from apps import AbstractApp


class Example:
"""
Just another class, feel free to make it do whatever you want
"""
def foobar(self) -> str:
"""
This method just returns some text, yours can do anything you want
:return: str
"""
return "Hello World!"


class App(AbstractApp):
"""
This class provides the screen methods needed by epdtext
"""

# Add an instance of our Example class
example: Example = Example()

def reload(self):
"""
This method should draw the contents of the screen to self.image
"""

# self.blank() resets self.image to a blank image
self.blank()

# self.text(text) draws the text to self.image
# Optional parameters include font, font_size, position, and color
self.text(self.example.foobar(), font_size=40, position=(50, 50))

def run_iteration(self):
"""
This method is optional, and will be run once per cycle
"""
# Do whatever you need to do, but try to make sure it doesn't take too long

# This line is very important, it keeps the auto reload working
super().run_iteration()

def touch(self, position: tuple):
"""
Called when the user taps the screen while the app is active
:param position: tuple coordinates (in pixels) of the tap
:return: None
"""
pass

0 comments on commit 156db23

Please sign in to comment.