menu.py 8.54 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
BUTTON_TIMER_POPPED = -1
COLOR1, COLOR2 = (color.CHAOSBLUE_DARK, color.CHAOSBLUE)
MAXCHARS = 11
19
HOMEAPP = "main.py"
20

Daniel Hoffend's avatar
Daniel Hoffend committed
21

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

Daniel Hoffend's avatar
Daniel Hoffend committed
28

29
30
31
32
33
34
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
35
    except Exception as e:
36
        print("Failed to read metadata for %s" % (app_folder))
37
        sys.print_exception(e)
Daniel Hoffend's avatar
Daniel Hoffend committed
38
39
40
        return {
            "author": "",
            "name": app_folder,
41
            "description": "",
Daniel Hoffend's avatar
Daniel Hoffend committed
42
43
44
45
            "category": "",
            "revision": 0,
        }

Rahix's avatar
Rahix committed
46
47
48

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

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

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

71
72
    # list all hatchary style apps (not .elf and not .py)
    # with or without metadata.json
73
    for appFolder in dirlist:
Daniel Hoffend's avatar
Daniel Hoffend committed
74
        if not (appFolder.endswith(".py") or appFolder.endswith(".elf")):
75
76
77
78
79
80
            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
81

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

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

    return apps
Rahix's avatar
Rahix committed
115

Daniel Hoffend's avatar
Daniel Hoffend committed
116

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

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

        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

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

Daniel Hoffend's avatar
Daniel Hoffend committed
147
148

def triangle(disp, x, y, left, scale=6, color=[255, 0, 0]):
149
150
151
152
153
    """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
154

Daniel Hoffend's avatar
Daniel Hoffend committed
155

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

159
160
    start = 0
    if pos > 0:
Daniel Hoffend's avatar
Daniel Hoffend committed
161
        start = pos - 1
162
163
164
165
166
167
168
169
170
171
    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
172
173
174
175
176
                0,
                (i - start) * 20,
                159,
                (i - start) * 20 + 20,
                col=COLOR1 if i == pos else COLOR2,
177
178
            )

Daniel Hoffend's avatar
Daniel Hoffend committed
179
            line = app[1]["name"]
180
181
182
183
184
            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
185
186
187
188
189
                off = (
                    lineoffset
                    if lineoffset + (MAXCHARS - 1) < linelength
                    else linelength - (MAXCHARS - 1)
                )
190
191
192
193
            if lineoffset > linelength:
                off = 0

            disp.print(
Daniel Hoffend's avatar
Daniel Hoffend committed
194
                " " + line[off : (off + (MAXCHARS - 1))],
195
                posy=(i - start) * 20,
Daniel Hoffend's avatar
Daniel Hoffend committed
196
                bg=COLOR1 if i == pos else COLOR2,
197
198
199
200
201
202
203
204
205
206
207
208
209
            )
            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
210
211
212
213
    disp.update()


def main():
214
    create_folders()
Rahix's avatar
Rahix committed
215
216
217
218
    disp = display.open()
    applist = list_apps()
    numapps = len(applist)
    current = 0
219
220
221
222
    lineoffset = 0
    timerscrollspeed = 1
    timerstartscroll = 5
    timercountpopped = 0
223
    timerinactivity = 100
224
    for ev in button_events(10):
koalo's avatar
koalo committed
225
        if numapps == 0:
Rahix's avatar
Rahix committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
            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
241
242
243
            disp.update()
            continue

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

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

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

264
265
266
267
268
269
270
271
272
273
            if applist[0][0] == "/%s" % HOMEAPP and timercountpopped >= timerinactivity:
                print("Inactivity timer popped")
                disp.clear().update()
                disp.close()
                try:
                    os.exec("/%s" % HOMEAPP)
                except OSError as e:
                    print("Loading failed: ", e)
                    os.exit(1)

Rahix's avatar
Rahix committed
274
275
276
277
278
        elif ev == buttons.TOP_RIGHT:
            # Select & start
            disp.clear().update()
            disp.close()
            try:
279
                os.exec(applist[current][0])
Rahix's avatar
Rahix committed
280
281
282
283
            except OSError as e:
                print("Loading failed: ", e)
                os.exit(1)

284
        draw_menu(disp, applist, current, numapps, lineoffset)
Rahix's avatar
Rahix committed
285
286
287


if __name__ == "__main__":
Rahix's avatar
Rahix committed
288
289
290
291
292
293
294
295
296
297
298
    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)