Skip to content

Commit

Permalink
Merge pull request #55 from emfcamp/settings_app
Browse files Browse the repository at this point in the history
Add basic read-only settings app
  • Loading branch information
MatthewWilkes committed May 28, 2024
2 parents 7d74863 + 7e34d3c commit b078785
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 1 deletion.
196 changes: 196 additions & 0 deletions modules/app_components/layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
from events.input import BUTTON_TYPES
from . import tokens, utils


class Layoutable:
height: int # Available after draw

def __init__(self):
self.height = 0

def draw(self, ctx, focused=False):
return

async def button_event(self, event):
return False


class TextDisplay(Layoutable):
def __init__(self, text, font_size=None, rgb=None):
super().__init__()
self.text = text
if font_size is None:
font_size = tokens.label_font_size
self.font_size = font_size
self.lines = None
if rgb is None:
rgb = tokens.ui_colors["label"]
self.rgb = rgb

def draw(self, ctx, focused=False):
ctx.save()
if self.lines is None:
self.lines = utils.wrap_text(ctx, self.text)
self.height = len(self.lines) * ctx.font_size
ctx.text_align = ctx.LEFT
if self.rgb:
ctx.rgb(*self.rgb)
for i, line in enumerate(self.lines):
ctx.move_to(0, i * ctx.font_size)
ctx.text(line)
ctx.restore()


class ButtonDisplay(Layoutable):
def __init__(self, text, font_size=None, rgb=None):
self.text = text
self.height = 40

def draw(self, ctx, focused=False):
ctx.save()

# Draw button
ctx.translate(30, 5)
ctx.scale(0.75, 0.75)
if focused:
bg = tokens.ui_colors["active_button_background"]
fg = tokens.ui_colors["active_button_text"]
else:
bg = tokens.ui_colors["button_background"]
fg = tokens.ui_colors["active_button_text"]
ctx.rgb(*bg)
ctx.round_rectangle(0, 0, tokens.display_x, 40, 30).fill()

# Draw text
ctx.rgb(*fg)
ctx.move_to(120, 30)
ctx.text_align = ctx.CENTER
ctx.text(self.text)

ctx.restore()


class DefinitionDisplay(Layoutable):
def __init__(self, label, value):
self.label = label
self.value = value
self.height = 0

def draw(self, ctx, focused=False):
ctx.save()
self.height = 0

# Draw heading
ctx.font_size = tokens.one_pt * 8
ctx.text_align = ctx.LEFT
if focused:
ctx.rgb(*tokens.colors["orange"])
else:
ctx.rgb(*tokens.colors["yellow"])

# Draw label
label_lines = utils.wrap_text(ctx, self.label)
for line in label_lines:
ctx.move_to(0, self.height)
ctx.text(line)
self.height += ctx.font_size

ctx.rgb(*tokens.ui_colors["label"])

# Draw value
ctx.font_size = tokens.ten_pt
value_lines = utils.wrap_text(ctx, self.value, width=230)
for line in value_lines:
ctx.move_to(10, self.height)
ctx.text(line)
self.height += ctx.font_size

ctx.restore()


class LinearLayout(Layoutable):
def __init__(self, items):
self.items = items
self.y_offset = 120
self.scale_factor = 0.9
super().__init__()

def draw(self, ctx):
focused_child = self.centred_component()
self.height = 0

ctx.save()
# Clip to the screen to be shown
ctx.rectangle(-120, -120, 240, 240).clip()

# Re-centre so the origin is in the top left
# Use y_offset to move this down
ctx.translate(-120, -120 + self.y_offset)

# Scale to 90% and centre
ctx.scale(self.scale_factor, self.scale_factor)
ctx.translate(12, 0)

# Draw each item in turn
for item in self.items:
item.draw(ctx, focused=item == focused_child)
ctx.translate(0, item.height)
self.height += item.height

ctx.restore()

def centred_component(self):
cumulative_height = 0
for item in self.items:
cumulative_height += item.height * self.scale_factor
print(f"Y: {self.y_offset}, cumulative: {cumulative_height}")
if round(cumulative_height) > round(120 - self.y_offset):
return item
return item

async def button_event(self, event) -> bool:
focused = self.centred_component()
to_jump = min(focused.height * self.scale_factor, 60)
print(f"Y: {self.y_offset}, Jump: {to_jump}")
if BUTTON_TYPES["UP"] in event.button:
self.y_offset += to_jump
if self.y_offset > 120:
self.y_offset = 120
return True
elif BUTTON_TYPES["DOWN"] in event.button:
self.y_offset -= to_jump
if self.y_offset < 120 - self.height:
self.y_offset = 120 - self.height
return True
else:
return await focused.button_event(event)


a = """
import display, time
import app_components.layout
text = app_components.layout.TextDisplay("Lorem ipsum " + "abcde "*30)
foo = app_components.layout.ButtonDisplay("foo")
bar = app_components.layout.DefinitionDisplay("Wifi", "emfcamp")
layout = app_components.layout.LinearLayout([text, foo, bar])
ctx=display.get_ctx()
app_components.clear_background(ctx)
ctx.rgb(1,1,1)
layout.draw(ctx)
display.end_frame(ctx)
"""

"""
def scroll():
for i in range(20):
ctx=display.get_ctx()
app_components.clear_background(ctx)
ctx.rgb(1,1,1)
layout.draw(ctx)
display.end_frame(ctx)
time.sleep_ms(100)
layout.y_offset -= 10
"""
11 changes: 10 additions & 1 deletion modules/app_components/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@
"orange": (246, 127, 2),
"pink": (245, 80, 137),
"blue": (46, 173, 217),
"black": (0, 0, 0),
"white": (255, 255, 255),
}

colors = {
name: (c[0] / 256.0, c[1] / 256.0, c[2] / 256.0) for (name, c) in colors.items()
}

ui_colors = {"background": colors["dark_green"], "label": (232, 230, 227)}
ui_colors = {
"background": colors["dark_green"],
"label": colors["white"],
"button_background": colors["pale_green"],
"button_text": colors["black"],
"active_button_background": colors["yellow"],
"active_button_text": colors["black"],
}


def clear_background(ctx):
Expand Down
21 changes: 21 additions & 0 deletions modules/app_components/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
def fill_line(ctx, text, width_for_line):
extra_text = ""
text_that_fits = text
text_width = ctx.text_width(text_that_fits)
while text_width > width_for_line:
character = text_that_fits[-1]
text_that_fits = text_that_fits[:-1]
extra_text = character + extra_text
text_width = ctx.text_width(text_that_fits)
return text_that_fits, extra_text


def wrap_text(ctx, text, width=None):
if width is None:
width = 240
remaining_text = text
lines = []
while remaining_text:
line, remaining_text = fill_line(ctx, remaining_text, width)
lines.append(line)
return lines
51 changes: 51 additions & 0 deletions modules/firmware_apps/settings_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import settings
import app
from app_components import layout, tokens
from events.input import BUTTON_TYPES, ButtonDownEvent
from system.eventbus import eventbus
from system.scheduler.events import RequestForegroundPushEvent


def string_formatter(value):
if value is None:
return "Default"
else:
return str(value)


class SettingsApp(app.App):
def __init__(self):
self.layout = layout.LinearLayout(items=[])
self.make_layout_children()
eventbus.on_async(ButtonDownEvent, self._button_handler, self)
eventbus.on(RequestForegroundPushEvent, self.make_layout_children, self)

async def _button_handler(self, event):
layout_handled = await self.layout.button_event(event)
if not layout_handled:
if BUTTON_TYPES["CANCEL"] in event.button:
self.minimise()

def make_layout_children(self, event=None):
self.layout.items = []
for id, label, formatter in self.settings_options():
value = settings.get(id)
self.layout.items.append(layout.DefinitionDisplay(label, formatter(value)))

def settings_options(self):
return [
("name", "Name", string_formatter),
("pattern", "LED Pattern", string_formatter),
("wifi_tx_power", "WiFi TX power", string_formatter),
("wifi_connection_timeout", "WiFi connection timeout", string_formatter),
("wifi_ssid", "WiFi SSID", string_formatter),
("wifi_password", "WiFi password", string_formatter),
("wifi_wpa2ent_username", "WPA2 Enterprise Username", string_formatter),
]

def update(self, delta):
return True

def draw(self, ctx):
tokens.clear_background(ctx)
self.layout.draw(ctx)
1 change: 1 addition & 0 deletions modules/system/launcher/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def list_core_apps(self):
# ("Magnetometer", "magnet_app", "Magnetometer"),
("Update", "system.ota.ota", "OtaUpdate"),
("Power Off", "firmware_apps.poweroff", "PowerOff"),
("Settings", "firmware_apps.settings_app", "SettingsApp"),
# ("Settings", "settings_app", "SettingsApp"),
]
core_apps = []
Expand Down

0 comments on commit b078785

Please sign in to comment.