Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 74 additions & 24 deletions apps/home/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@

A tap on a tab switches pages at once; a swipe across the page slides to
the next one. Home comes back to the tab it was last on.

On a round screen (see ``appkit.shell.is_round``) the pages and their tabs
fill the square inside the circle, two apps by two, and the gear sits
centred above them, so nothing falls outside the circle.
"""

import lvgl as lv

from appkit.shell import ROUND_MARGIN, is_round
from touchkit import ui

DEFAULT_SYMBOL = "LIST"
Expand All @@ -30,34 +35,47 @@
TAB_ON = 0x1A2438


def _grid(scope, page, W, H, apps):
def _grid(scope, page, cw, ch, apps, cols=COLS, rows=ROWS, small=False):
"""The app tiles on one page: a card *cw* by *ch*, *cols* by *rows* tiles.
*small* tiles take the smaller fonts and stack the icon over the title."""
launcher = scope.launcher
cw, ch = W - 2 * MARGIN, H - TOP - TAB_BAR - 2 * MARGIN
gap = GAP // 2 if small else GAP
card = lv.obj(page)
card.remove_style_all()
card.set_pos(MARGIN, MARGIN)
card.set_size(cw, ch)
card.set_style_bg_color(lv.color_hex(CARD), 0)
card.set_style_bg_opa(lv.OPA.COVER, 0)
card.set_style_radius(20, 0)
card.set_style_radius(12 if small else 20, 0)
card.set_style_border_width(1, 0)
card.set_style_border_color(lv.color_hex(LINE), 0)
card.remove_flag(lv.obj.FLAG.SCROLLABLE)
card.add_flag(lv.obj.FLAG.EVENT_BUBBLE) # a swipe on the card still turns the page
w = (cw - (COLS + 1) * GAP) // COLS
h = (ch - (ROWS + 1) * GAP) // ROWS
for i, (name, sym, color) in enumerate(apps[: COLS * ROWS]):
col, row = i % COLS, i // COLS
w = (cw - (cols + 1) * gap) // cols
h = (ch - (rows + 1) * gap) // rows
for i, (name, sym, color) in enumerate(apps[: cols * rows]):
col, row = i % cols, i // cols
b = lv.button(card)
b.set_size(w, h)
b.set_pos(GAP + col * (w + GAP), GAP + row * (h + GAP))
b.set_pos(gap + col * (w + gap), gap + row * (h + gap))
b.set_style_bg_color(lv.color_hex(color), 0)
b.set_style_radius(18, 0)
icon = ui.label(b, sym, ui.huge, ui.WHITE)
icon.align(lv.ALIGN.CENTER, 0, -22)
title = launcher.info.get(name, {}).get("title", name)
lbl = ui.label(b, title, ui.font, ui.WHITE)
lbl.align(lv.ALIGN.CENTER, 0, 40)
if small:
b.set_style_radius(10, 0)
b.set_style_pad_all(2, 0)
icon = ui.label(b, sym, ui.big, ui.WHITE)
icon.align(lv.ALIGN.CENTER, 0, -ui.tiny.line_height // 2)
lbl = ui.label(b, title, ui.tiny, ui.WHITE)
lbl.set_long_mode(lv.label.LONG_MODE.DOTS)
lbl.set_width(w - 4)
lbl.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0)
lbl.align(lv.ALIGN.BOTTOM_MID, 0, 0)
else:
b.set_style_radius(18, 0)
icon = ui.label(b, sym, ui.huge, ui.WHITE)
icon.align(lv.ALIGN.CENTER, 0, -22)
lbl = ui.label(b, title, ui.font, ui.WHITE)
lbl.align(lv.ALIGN.CENTER, 0, 40)
b.add_event_cb((lambda n: lambda e: scope.switch(n))(name), lv.EVENT.CLICKED, None)


Expand Down Expand Up @@ -94,23 +112,52 @@ def main(scope):
for name in scope.launcher.names:
if info.get(name, {}).get("gear"):
gear_app = name
title = ui.label(scr, "Home", ui.big, ui.ACCENT)
title.set_pos(GAP + 4, 14)
# Dark whether or not the shell put a dark theme on the display.
scr.set_style_bg_color(ui.BG, 0)
scr.set_style_bg_opa(lv.OPA.COVER, 0)
# A round panel gets its own layout when Home has the whole screen.
round_ = W == H and getattr(scope, "rect", (0, 0, W, H))[:2] == (0, 0) and is_round()

if round_:
# The square inside the circle holds the pages and their tabs; the
# gear goes in the cap of the circle above it, centred.
inner = W // 2 - ROUND_MARGIN
side = int(inner * 1.4142) & ~1
x0 = (W - side) // 2
tab_bar = ui.small.line_height + 14
gear_w = 48
# As high as the circle is gear_w wide there, and down to the square.
gear_y = W // 2 - int((inner * inner - (gear_w // 2) ** 2) ** 0.5) + 1
gear_h = max(24, min(TOP - 12, x0 - gear_y - 2))
else:
title = ui.label(scr, "Home", ui.big, ui.ACCENT)
title.set_pos(GAP + 4, 14)

if gear_app is not None:
gear = lv.button(scr)
gear.set_size(64, TOP - 12)
gear.align(lv.ALIGN.TOP_RIGHT, -GAP, 6)
gear.set_style_bg_color(lv.color_hex(0x44505E), 0)
gear.set_style_radius(12, 0)
ui.label(gear, lv.SYMBOL.SETTINGS, ui.big, ui.WHITE).center()
if round_:
gear.set_size(gear_w, gear_h)
gear.set_pos((W - gear_w) // 2, gear_y)
gear.set_style_radius(gear_h // 2, 0)
gear.set_style_pad_all(0, 0)
ui.label(gear, lv.SYMBOL.SETTINGS, ui.font, ui.WHITE).center()
else:
gear.set_size(64, TOP - 12)
gear.align(lv.ALIGN.TOP_RIGHT, -GAP, 6)
gear.set_style_radius(12, 0)
ui.label(gear, lv.SYMBOL.SETTINGS, ui.big, ui.WHITE).center()
gear.add_event_cb(lambda e: scope.switch(gear_app), lv.EVENT.CLICKED, None)

tv = lv.tabview(scr)
tv.set_pos(0, TOP)
tv.set_size(W, H - TOP)
if round_:
tv.set_pos(x0, x0)
tv.set_size(side, side)
else:
tv.set_pos(0, TOP)
tv.set_size(W, H - TOP)
tv.set_tab_bar_position(lv.DIR.BOTTOM)
tv.set_tab_bar_size(TAB_BAR)
tv.set_tab_bar_size(tab_bar if round_ else TAB_BAR)
tv.set_style_bg_color(ui.BG, 0)
tv.get_content().set_style_bg_color(ui.BG, 0)

Expand All @@ -120,7 +167,10 @@ def main(scope):
page.set_style_pad_all(0, 0)
page.set_style_bg_color(ui.BG, 0)
page.remove_flag(lv.obj.FLAG.SCROLLABLE)
_grid(scope, page, W, H, apps)
if round_:
_grid(scope, page, side - 2 * MARGIN, side - tab_bar - 2 * MARGIN, apps, 2, 2, True)
else:
_grid(scope, page, W - 2 * MARGIN, H - TOP - TAB_BAR - 2 * MARGIN, apps)

bar = tv.get_tab_bar()
bar.set_style_bg_color(ui.PANEL, 0)
Expand All @@ -132,7 +182,7 @@ def main(scope):
for i in range(bar.get_child_count()):
t = bar.get_child(i)
t.set_style_radius(0, 0)
t.set_style_text_font(ui.font, 0)
t.set_style_text_font(ui.small if round_ else ui.font, 0)
t.set_style_text_color(ui.MUTED, 0)
t.set_style_bg_opa(lv.OPA.TRANSP, 0)
t.set_style_border_width(0, 0)
Expand Down
14 changes: 8 additions & 6 deletions apps/system/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,12 +786,14 @@ def close():
_st["seconds"] = None
if not measuring and hasattr(display_drv, "measure_fps"):
display_drv.measure_fps(False)
try:
import wifi_manager

wifi_manager.cancel()
except Exception:
pass
# Only when Wi-Fi setup loaded it: importing it here would keep a
# module in memory that System never used.
wifi_manager = sys.modules.get("wifi_manager")
if wifi_manager is not None:
try:
wifi_manager.cancel()
except Exception:
pass

scope.on_close(close)

Expand Down
3 changes: 2 additions & 1 deletion lib/appkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ home screen instead of its own list of buttons.
home screen. An app set that wants its own home screen, chrome or launcher
setup builds a `Launcher` itself. Its `Shell` subclass gives apps a smaller
area by overriding `make_root()`, and its own Home button by overriding
`make_home_button()`.
`make_home_button()`. The default Home button sits at the top right; on a
round screen it's a small round button on the diagonal, inside the circle.

[`examples/appkit_simpletest`](examples/appkit_simpletest) is an app set of
two apps, a counter and a greeter, laid out this way.
Expand Down
26 changes: 26 additions & 0 deletions lib/appkit/foreground.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def frame():
_token = None # changes with the function: a stale timer step sees it
_calls = 0 # calls of the foreground function in progress
_after_calls = [] # what to run once no call is in progress
_turns = [] # what runs at the loop's next turn (`next_turn`)
_serving = False
_stop = False

Expand Down Expand Up @@ -169,6 +170,29 @@ def serving():
return _serving


def next_turn(fn, arg):
"""Call ``fn(arg)`` from `serve`'s loop at its next turn, outside every
callback, and return True; return False when nothing runs the loop.

The launcher starts the next app this way after closing one, so the
calls below the start are the loop's own and none made while the old app
ran. An error in *fn* is reported and the loop carries on.
"""
if not _serving:
return False
_turns.append((fn, arg))
return True


def _run_turns():
while _turns:
fn, arg = _turns.pop(0)
try:
fn(arg)
except Exception as exc:
_print_exc(exc)


def stop():
"""Have `serve` return after the call it's in."""
global _stop
Expand Down Expand Up @@ -292,6 +316,8 @@ def serve(app=None):
_serving = True
try:
while not _done(app):
if _turns:
_run_turns()
if console is not None:
console()
fn = _fn
Expand Down
Loading
Loading