menu.py 8.11 KB
Newer Older
Rahix's avatar
Rahix committed
1
2
3
4
5
6
7
8
9
10
11
"""
Menu Script
===========
You can customize this script however you want :)  If you want to go back to
the default version, just delete this file; the firmware will recreate it on
next run.
"""
import buttons
import color
import display
import os
12
import utime
13
14
15
import ujson
import sys

16
17
18
19
BUTTON_TIMER_POPPED = -1
COLOR1, COLOR2 = (color.CHAOSBLUE_DARK, color.CHAOSBLUE)
MAXCHARS = 11

Daniel Hoffend's avatar
Daniel Hoffend committed
20

21
22
23
24
25
26
def create_folders():
    try:
        os.mkdir("/apps")
    except:
        pass

Daniel Hoffend's avatar
Daniel Hoffend committed
27

28
29
30
31
32
33
def read_metadata(app_folder):
    try:
        info_file = "/apps/%s/metadata.json" % (app_folder)
        with open(info_file) as f:
            information = f.read()
        return ujson.loads(information)
Rahix's avatar
Rahix committed
34
    except Exception as e:
35
        print("Failed to read metadata for %s" % (app_folder))
36
        sys.print_exception(e)
Daniel Hoffend's avatar
Daniel Hoffend committed
37
38
39
        return {
            "author": "",
            "name": app_folder,
40
            "description": "",
Daniel Hoffend's avatar
Daniel Hoffend committed
41
42
43
44
            "category": "",
            "revision": 0,
        }

Rahix's avatar
Rahix committed
45
46
47

def list_apps():
    """Create a list of available apps."""
48
    apps = []
Rahix's avatar
Rahix committed
49

50
51
52
    # add main application
    for mainFile in os.listdir("/"):
        if mainFile == "main.py":
Daniel Hoffend's avatar
Daniel Hoffend committed
53
54
55
56
57
            apps.append(
                [
                    "/main.py",
                    {
                        "author": "card10badge Team",
Daniel Hoffend's avatar
Daniel Hoffend committed
58
                        "name": "Home",
Daniel Hoffend's avatar
Daniel Hoffend committed
59
60
61
62
63
64
                        "description": "",
                        "category": "",
                        "revision": 0,
                    },
                ]
            )
Rahix's avatar
Rahix committed
65

66
67
68
69
    dirlist = [
        entry for entry in sorted(os.listdir("/apps")) if not entry.startswith(".")
    ]

70
71
    # list all hatchary style apps (not .elf and not .py)
    # with or without metadata.json
72
    for appFolder in dirlist:
Daniel Hoffend's avatar
Daniel Hoffend committed
73
        if not (appFolder.endswith(".py") or appFolder.endswith(".elf")):
74
75
76
77
78
79
            metadata = read_metadata(appFolder)
            if not metadata.get("bin", None):
                fileName = "/apps/%s/__init__.py" % appFolder
            else:
                fileName = "/apps/%s/%s" % (appFolder, metadata["bin"])
            apps.append([fileName, metadata])
Rahix's avatar
Rahix committed
80

81
    # list simple python scripts
82
    for pyFile in dirlist:
83
        if pyFile.endswith(".py"):
Daniel Hoffend's avatar
Daniel Hoffend committed
84
85
86
87
88
89
90
91
92
93
94
95
            apps.append(
                [
                    "/apps/%s" % pyFile,
                    {
                        "author": "",
                        "name": pyFile,
                        "description": "",
                        "category": "",
                        "revision": 0,
                    },
                ]
            )
Rahix's avatar
Rahix committed
96

97
    # list simple elf binaries
98
    for elfFile in dirlist:
99
        if elfFile.endswith(".elf"):
Daniel Hoffend's avatar
Daniel Hoffend committed
100
101
102
103
104
105
106
107
108
109
110
111
            apps.append(
                [
                    "/apps/%s" % elfFile,
                    {
                        "author": "",
                        "name": elfFile,
                        "description": "",
                        "category": "",
                        "revision": 0,
                    },
                ]
            )
112
113

    return apps
Rahix's avatar
Rahix committed
114

Daniel Hoffend's avatar
Daniel Hoffend committed
115

116
def button_events(timeout=0):
Rahix's avatar
Rahix committed
117
118
119
    """Iterate over button presses (event-loop)."""
    yield 0
    button_pressed = False
120
    count = 0
Rahix's avatar
Rahix committed
121
122
    while True:
        v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT)
123
124
125
126
127
        if timeout > 0 and count > 0 and count % timeout == 0:
            yield BUTTON_TIMER_POPPED

        if timeout > 0:
            count += 1
Rahix's avatar
Rahix committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

        if v == 0:
            button_pressed = False

        if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
            button_pressed = True
            yield buttons.BOTTOM_LEFT

        if not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
            button_pressed = True
            yield buttons.BOTTOM_RIGHT

        if not button_pressed and v & buttons.TOP_RIGHT != 0:
            button_pressed = True
            yield buttons.TOP_RIGHT

144
        utime.sleep_ms(10)
Rahix's avatar
Rahix committed
145

Daniel Hoffend's avatar
Daniel Hoffend committed
146
147

def triangle(disp, x, y, left, scale=6, color=[255, 0, 0]):
148
149
150
151
152
    """Draw a triangle to show there's more text in this line"""
    yf = 1 if left else -1
    disp.line(x - scale * yf, int(y + scale / 2), x, y, col=color)
    disp.line(x, y, x, y + scale, col=color)
    disp.line(x, y + scale, x - scale * yf, y + int(scale / 2), col=color)
Rahix's avatar
Rahix committed
153

Daniel Hoffend's avatar
Daniel Hoffend committed
154

155
def draw_menu(disp, applist, pos, appcount, lineoffset):
Rahix's avatar
Rahix committed
156
157
    disp.clear()

158
159
    start = 0
    if pos > 0:
Daniel Hoffend's avatar
Daniel Hoffend committed
160
        start = pos - 1
161
162
163
164
165
166
167
168
169
170
    if start + 4 > appcount:
        start = appcount - 4
    if start < 0:
        start = 0

    for i, app in enumerate(applist):
        if i >= start + 4 or i >= appcount:
            break
        if i >= start:
            disp.rect(
Daniel Hoffend's avatar
Daniel Hoffend committed
171
172
173
174
175
                0,
                (i - start) * 20,
                159,
                (i - start) * 20 + 20,
                col=COLOR1 if i == pos else COLOR2,
176
177
            )

Daniel Hoffend's avatar
Daniel Hoffend committed
178
            line = app[1]["name"]
179
180
181
182
183
            linelength = len(line)
            off = 0

            # calc line offset for scrolling
            if i == pos and linelength > (MAXCHARS - 1) and lineoffset > 0:
Daniel Hoffend's avatar
Daniel Hoffend committed
184
185
186
187
188
                off = (
                    lineoffset
                    if lineoffset + (MAXCHARS - 1) < linelength
                    else linelength - (MAXCHARS - 1)
                )
189
190
191
192
            if lineoffset > linelength:
                off = 0

            disp.print(
Daniel Hoffend's avatar
Daniel Hoffend committed
193
                " " + line[off : (off + (MAXCHARS - 1))],
194
                posy=(i - start) * 20,
Daniel Hoffend's avatar
Daniel Hoffend committed
195
                bg=COLOR1 if i == pos else COLOR2,
196
197
198
199
200
201
202
203
204
205
206
207
208
            )
            if i == pos:
                disp.print(">", posy=(i - start) * 20, fg=color.COMMYELLOW, bg=COLOR1)

            if linelength > (MAXCHARS - 1) and off < linelength - (MAXCHARS - 1):
                triangle(disp, 153, (i - start) * 20 + 6, False, 6)
                triangle(disp, 154, (i - start) * 20 + 7, False, 4)
                triangle(disp, 155, (i - start) * 20 + 8, False, 2)
            if off > 0:
                triangle(disp, 24, (i - start) * 20 + 6, True, 6)
                triangle(disp, 23, (i - start) * 20 + 7, True, 4)
                triangle(disp, 22, (i - start) * 20 + 8, True, 2)

Rahix's avatar
Rahix committed
209
210
211
212
    disp.update()


def main():
213
    create_folders()
Rahix's avatar
Rahix committed
214
215
216
217
    disp = display.open()
    applist = list_apps()
    numapps = len(applist)
    current = 0
218
219
220
221
222
    lineoffset = 0
    timerscrollspeed = 1
    timerstartscroll = 5
    timercountpopped = 0
    for ev in button_events(10):
koalo's avatar
koalo committed
223
        if numapps == 0:
Rahix's avatar
Rahix committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
            disp.clear(color.COMMYELLOW)
            disp.print(
                " No apps ",
                posx=17,
                posy=20,
                fg=color.COMMYELLOW_DARK,
                bg=color.COMMYELLOW,
            )
            disp.print(
                "available",
                posx=17,
                posy=40,
                fg=color.COMMYELLOW_DARK,
                bg=color.COMMYELLOW,
            )
koalo's avatar
koalo committed
239
240
241
            disp.update()
            continue

Rahix's avatar
Rahix committed
242
243
244
        if ev == buttons.BOTTOM_RIGHT:
            # Scroll down
            current = (current + 1) % numapps
245
246
247
            lineoffset = 0
            timercountpopped = 0

Rahix's avatar
Rahix committed
248
249
250
        elif ev == buttons.BOTTOM_LEFT:
            # Scroll up
            current = (current + numapps - 1) % numapps
251
252
253
254
255
            lineoffset = 0
            timercountpopped = 0

        elif ev == BUTTON_TIMER_POPPED:
            timercountpopped += 1
Daniel Hoffend's avatar
Daniel Hoffend committed
256
257
258
259
            if (
                timercountpopped >= timerstartscroll
                and (timercountpopped - timerstartscroll) % timerscrollspeed == 0
            ):
260
261
                lineoffset += 1

Rahix's avatar
Rahix committed
262
263
264
265
266
        elif ev == buttons.TOP_RIGHT:
            # Select & start
            disp.clear().update()
            disp.close()
            try:
267
                os.exec(applist[current][0])
Rahix's avatar
Rahix committed
268
269
270
271
            except OSError as e:
                print("Loading failed: ", e)
                os.exit(1)

272
        draw_menu(disp, applist, current, numapps, lineoffset)
Rahix's avatar
Rahix committed
273
274
275


if __name__ == "__main__":
Rahix's avatar
Rahix committed
276
277
278
279
280
281
282
283
284
285
286
    try:
        main()
    except Exception as e:
        sys.print_exception(e)
        with display.open() as d:
            d.clear(color.COMMYELLOW)
            d.print("Menu", posx=52, posy=20, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW)
            d.print("crashed", posx=31, posy=40, fg=color.COMMYELLOW_DARK, bg=color.COMMYELLOW)
            d.update()
            utime.sleep(2)
        os.exit(1)