Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit22fda1e

Browse files
author
Quasi Kili
committed
first working version but navigation in modal is horrible
1 parent816d3a3 commit22fda1e

File tree

4 files changed

+340
-1
lines changed

4 files changed

+340
-1
lines changed

‎internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py‎

Lines changed: 294 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
importtime
1717
importuhashlib
1818
importubinascii
19+
import_thread
20+
21+
# Mode constants
22+
MODE_LAUNCHING=0
23+
MODE_UNINSTALL=1
1924

2025

2126
classLauncher(Activity):
@@ -24,6 +29,11 @@ def __init__(self):
2429
# Cache of the last app list + a quick hash of the icons
2530
self._last_app_list=None# list of tuples (name, path, icon_hash)
2631
self._last_ui_built=False# was UI built at least once?
32+
self._current_mode=MODE_LAUNCHING# current launcher mode
33+
self._mode_button=None# reference to mode toggle button
34+
self._mode_button_icon=None# reference to mode button icon
35+
self._mode_button_label=None# reference to mode button label
36+
self._app_widgets= []# list to store app widget references
2737

2838
defonCreate(self):
2939
print("launcher.py onCreate()")
@@ -34,8 +44,98 @@ def onCreate(self):
3444
main_screen.set_style_pad_hor(mpos.ui.pct_of_display_width(2),0)
3545
main_screen.set_style_pad_ver(mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT,0)
3646
main_screen.set_flex_flow(lv.FLEX_FLOW.ROW_WRAP)
47+
3748
self.setContentView(main_screen)
3849

50+
# ------------------------------------------------------------------
51+
def_create_mode_button(self,screen,icon_size,label_height,iconcont_width,iconcont_height,focusgroup):
52+
"""Create the mode toggle button as an app-like icon in the grid"""
53+
importos
54+
55+
# ----- container (same as regular apps) -------------------------
56+
mode_cont=lv.obj(screen)
57+
mode_cont.set_size(iconcont_width,iconcont_height)
58+
mode_cont.set_style_border_width(0,lv.PART.MAIN)
59+
mode_cont.set_style_pad_all(0,0)
60+
mode_cont.set_style_bg_opa(lv.OPA.TRANSP,0)
61+
mode_cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
62+
63+
# ----- icon -----------------------------------------------------
64+
mode_icon=lv.image(mode_cont)
65+
mode_icon.align(lv.ALIGN.TOP_MID,0,0)
66+
mode_icon.set_size(icon_size,icon_size)
67+
68+
# Load appropriate icon based on mode
69+
ifself._current_mode==MODE_LAUNCHING:
70+
# Show trashcan icon (enter uninstall mode)
71+
try:
72+
app_dir=os.path.dirname(os.path.dirname(__file__))
73+
icon_path=app_dir+"/res/mipmap-mdpi/trashcan_icon.png"
74+
icon_dsc=self.create_icon_dsc(icon_path)
75+
mode_icon.set_src(icon_dsc)
76+
exceptExceptionase:
77+
print(f"Failed to load trashcan icon:{e}")
78+
mode_icon.set_src(lv.SYMBOL.TRASH)
79+
label_text="Uninstall Apps"
80+
else:
81+
# Show exit icon (exit uninstall mode)
82+
try:
83+
app_dir=os.path.dirname(os.path.dirname(__file__))
84+
icon_path=app_dir+"/res/mipmap-mdpi/exit_icon.png"
85+
icon_dsc=self.create_icon_dsc(icon_path)
86+
mode_icon.set_src(icon_dsc)
87+
exceptExceptionase:
88+
print(f"Failed to load exit icon:{e}")
89+
mode_icon.set_src(lv.SYMBOL.CLOSE)
90+
label_text="Exit Uninstall"
91+
92+
# ----- label (same as regular apps) -----------------------------
93+
mode_label=lv.label(mode_cont)
94+
mode_label.set_text(label_text)
95+
mode_label.set_long_mode(lv.label.LONG_MODE.WRAP)
96+
mode_label.set_width(iconcont_width)
97+
mode_label.align(lv.ALIGN.BOTTOM_MID,0,0)
98+
mode_label.set_style_text_align(lv.TEXT_ALIGN.CENTER,0)
99+
100+
# ----- events ---------------------------------------------------
101+
mode_cont.add_event_cb(
102+
lambdae:self._toggle_mode(),
103+
lv.EVENT.CLICKED,None)
104+
mode_cont.add_event_cb(
105+
lambdae,cont=mode_cont:self.focus_app_cont(cont),
106+
lv.EVENT.FOCUSED,None)
107+
mode_cont.add_event_cb(
108+
lambdae,cont=mode_cont:self.defocus_app_cont(cont),
109+
lv.EVENT.DEFOCUSED,None)
110+
111+
iffocusgroup:
112+
focusgroup.add_obj(mode_cont)
113+
114+
# Store references
115+
self._mode_button=mode_cont
116+
self._mode_button_icon=mode_icon
117+
self._mode_button_label=mode_label
118+
119+
# ------------------------------------------------------------------
120+
def_toggle_mode(self):
121+
"""Toggle between launching and uninstall modes"""
122+
ifself._current_mode==MODE_LAUNCHING:
123+
self._current_mode=MODE_UNINSTALL
124+
else:
125+
self._current_mode=MODE_LAUNCHING
126+
127+
# Force UI rebuild to update mode button and app overlays
128+
# self._last_ui_built = False
129+
# # Trigger onResume to rebuild
130+
# screen = self.getContentView()
131+
# Force UI rebuild to update mode button and app overlays
132+
self._last_app_list=None# Invalidate cache
133+
# Trigger onResume to rebuild with the active screen
134+
screen=lv.screen_active()
135+
ifscreen:
136+
self.onResume(screen)
137+
138+
39139
# ------------------------------------------------------------------
40140
# Helper: compute a cheap hash of a file (or return None if missing)
41141
@staticmethod
@@ -83,6 +183,9 @@ def onResume(self, screen):
83183
# 3. UI needs (re)building – clear screen and create widgets
84184
screen.clean()
85185

186+
# Clear app widgets list
187+
self._app_widgets= []
188+
86189
focusgroup=lv.group_get_default()
87190
ifnotfocusgroup:
88191
print("WARNING: could not get default focusgroup")
@@ -129,9 +232,48 @@ def onResume(self, screen):
129232
label.align(lv.ALIGN.BOTTOM_MID,0,0)
130233
label.set_style_text_align(lv.TEXT_ALIGN.CENTER,0)
131234

235+
# Store widget info
236+
widget_info= {
237+
'app':app,
238+
'container':app_cont,
239+
'image':image,
240+
'label':label,
241+
'overlay':None,
242+
'x_label':None
243+
}
244+
self._app_widgets.append(widget_info)
245+
246+
# ----- Add overlay if in uninstall mode --------------------
247+
ifself._current_mode==MODE_UNINSTALL:
248+
is_builtin=PackageManager.is_builtin_app(app.fullname)
249+
250+
# Create overlay
251+
overlay=lv.obj(app_cont)
252+
overlay.set_size(icon_size,icon_size)
253+
overlay.align(lv.ALIGN.TOP_MID,0,0)
254+
overlay.set_style_radius(8,0)
255+
overlay.set_style_border_width(0,0)
256+
widget_info['overlay']=overlay
257+
258+
ifis_builtin:
259+
# Grey out builtin apps
260+
overlay.set_style_bg_color(lv.color_hex(0x808080),0)
261+
overlay.set_style_bg_opa(lv.OPA._60,0)
262+
else:
263+
# Red X for non-builtin apps
264+
overlay.set_style_bg_color(lv.color_hex(0xE74C3C),0)
265+
overlay.set_style_bg_opa(lv.OPA._80,0)
266+
# Draw X
267+
x_label=lv.label(overlay)
268+
x_label.set_text(lv.SYMBOL.CLOSE)
269+
x_label.set_style_text_color(lv.color_hex(0xFFFFFF),0)
270+
x_label.set_style_text_font(lv.font_montserrat_32,0)
271+
x_label.center()
272+
widget_info['x_label']=x_label
273+
132274
# ----- events --------------------------------------------------
133275
app_cont.add_event_cb(
134-
lambdae,fullname=app.fullname:mpos.apps.start_app(fullname),
276+
lambdae,a=app:self._handle_app_click(a),
135277
lv.EVENT.CLICKED,None)
136278
app_cont.add_event_cb(
137279
lambdae,cont=app_cont:self.focus_app_cont(cont),
@@ -143,6 +285,10 @@ def onResume(self, screen):
143285
iffocusgroup:
144286
focusgroup.add_obj(app_cont)
145287

288+
# ------------------------------------------------------------------
289+
# Add mode toggle button as last item in grid
290+
self._create_mode_button(screen,icon_size,label_height,iconcont_width,iconcont_height,focusgroup)
291+
146292
# ------------------------------------------------------------------
147293
# 4. Store the new representation for the next resume
148294
self._last_app_list=current_apps
@@ -170,3 +316,150 @@ def focus_app_cont(self, app_cont):
170316

171317
defdefocus_app_cont(self,app_cont):
172318
app_cont.set_style_border_width(0,lv.PART.MAIN)
319+
320+
# ------------------------------------------------------------------
321+
def_handle_app_click(self,app):
322+
"""Handle app icon click based on current mode"""
323+
ifself._current_mode==MODE_LAUNCHING:
324+
# Normal launch
325+
mpos.apps.start_app(app.fullname)
326+
elifself._current_mode==MODE_UNINSTALL:
327+
# Check if builtin
328+
is_builtin=PackageManager.is_builtin_app(app.fullname)
329+
ifis_builtin:
330+
self._show_builtin_info_modal(app)
331+
else:
332+
self._show_uninstall_confirmation_modal(app)
333+
334+
# ------------------------------------------------------------------
335+
def_show_uninstall_confirmation_modal(self,app):
336+
"""Show confirmation modal for uninstalling an app"""
337+
# Create modal background on layer_top to ensure it's above everything
338+
try:
339+
parent=lv.layer_top()
340+
except:
341+
parent=lv.screen_active()
342+
343+
modal_bg=lv.obj(parent)
344+
modal_bg.set_size(lv.pct(100),lv.pct(100))
345+
modal_bg.set_style_bg_color(lv.color_hex(0x000000),0)
346+
modal_bg.set_style_bg_opa(lv.OPA._50,0)
347+
modal_bg.set_style_border_width(0,0)
348+
modal_bg.set_style_radius(0,0)
349+
modal_bg.set_pos(0,0)
350+
modal_bg.remove_flag(lv.obj.FLAG.SCROLLABLE)
351+
352+
# Create modal dialog
353+
modal=lv.obj(modal_bg)
354+
modal.set_size(lv.pct(90),lv.pct(90))
355+
modal.center()
356+
modal.set_style_pad_all(20,0)
357+
modal.set_flex_flow(lv.FLEX_FLOW.COLUMN)
358+
modal.set_flex_align(lv.FLEX_ALIGN.CENTER,lv.FLEX_ALIGN.CENTER,lv.FLEX_ALIGN.CENTER)
359+
360+
# Title
361+
title=lv.label(modal)
362+
title.set_text("Uninstall App?")
363+
title.set_style_text_font(lv.font_montserrat_20,0)
364+
365+
# Message
366+
msg=lv.label(modal)
367+
msg.set_text(f"Are you sure you want to uninstall{app.name}?")
368+
msg.set_style_text_align(lv.TEXT_ALIGN.CENTER,0)
369+
msg.set_width(lv.pct(90))
370+
371+
# Button container
372+
btn_cont=lv.obj(modal)
373+
btn_cont.set_size(lv.pct(100),lv.SIZE_CONTENT)
374+
btn_cont.set_style_border_width(0,0)
375+
btn_cont.set_style_pad_all(10,0)
376+
btn_cont.set_flex_flow(lv.FLEX_FLOW.ROW)
377+
btn_cont.set_flex_align(lv.FLEX_ALIGN.SPACE_EVENLY,lv.FLEX_ALIGN.CENTER,lv.FLEX_ALIGN.CENTER)
378+
379+
# Yes button
380+
yes_btn=lv.button(btn_cont)
381+
yes_btn.set_size(lv.pct(40),50)
382+
yes_btn.add_event_cb(lambdae,a=app,m=modal_bg:self._confirm_uninstall(a,m),lv.EVENT.CLICKED,None)
383+
yes_label=lv.label(yes_btn)
384+
yes_label.set_text("Yes")
385+
yes_label.center()
386+
387+
# No button
388+
no_btn=lv.button(btn_cont)
389+
no_btn.set_size(lv.pct(40),50)
390+
no_btn.add_event_cb(lambdae,m=modal_bg:self._close_modal(m),lv.EVENT.CLICKED,None)
391+
no_label=lv.label(no_btn)
392+
no_label.set_text("No")
393+
no_label.center()
394+
395+
# ------------------------------------------------------------------
396+
def_show_builtin_info_modal(self,app):
397+
"""Show info modal explaining builtin apps cannot be uninstalled"""
398+
# Create modal background on layer_top to ensure it's above everything
399+
try:
400+
parent=lv.layer_top()
401+
except:
402+
parent=lv.screen_active()
403+
404+
modal_bg=lv.obj(parent)
405+
modal_bg.set_size(lv.pct(100),lv.pct(100))
406+
modal_bg.set_style_bg_color(lv.color_hex(0x000000),0)
407+
modal_bg.set_style_bg_opa(lv.OPA._50,0)
408+
modal_bg.set_style_border_width(0,0)
409+
modal_bg.set_style_radius(0,0)
410+
modal_bg.set_pos(0,0)
411+
modal_bg.remove_flag(lv.obj.FLAG.SCROLLABLE)
412+
413+
# Create modal dialog
414+
modal=lv.obj(modal_bg)
415+
modal.set_size(lv.pct(90),lv.pct(90))
416+
modal.center()
417+
modal.set_style_pad_all(20,0)
418+
modal.set_flex_flow(lv.FLEX_FLOW.COLUMN)
419+
modal.set_flex_align(lv.FLEX_ALIGN.CENTER,lv.FLEX_ALIGN.CENTER,lv.FLEX_ALIGN.CENTER)
420+
421+
# Title
422+
title=lv.label(modal)
423+
title.set_text("Cannot Uninstall")
424+
title.set_style_text_font(lv.font_montserrat_20,0)
425+
426+
# Message
427+
msg=lv.label(modal)
428+
msg.set_text(f"{app.name} is a built-in app\nand cannot be uninstalled.")
429+
msg.set_style_text_align(lv.TEXT_ALIGN.CENTER,0)
430+
msg.set_width(lv.pct(90))
431+
432+
# OK button
433+
ok_btn=lv.button(modal)
434+
ok_btn.set_size(lv.pct(50),50)
435+
ok_btn.add_event_cb(lambdae,m=modal_bg:self._close_modal(m),lv.EVENT.CLICKED,None)
436+
ok_label=lv.label(ok_btn)
437+
ok_label.set_text("OK")
438+
ok_label.center()
439+
440+
# ------------------------------------------------------------------
441+
def_close_modal(self,modal_bg):
442+
"""Close and delete modal"""
443+
modal_bg.delete()
444+
445+
# ------------------------------------------------------------------
446+
def_confirm_uninstall(self,app,modal_bg):
447+
"""Actually uninstall the app"""
448+
self._close_modal(modal_bg)
449+
# Run uninstall in thread to avoid blocking UI
450+
try:
451+
_thread.stack_size(mpos.apps.good_stack_size())
452+
_thread.start_new_thread(self._uninstall_app_thread, (app.fullname,))
453+
exceptExceptionase:
454+
print(f"Could not start uninstall thread:{e}")
455+
456+
# ------------------------------------------------------------------
457+
def_uninstall_app_thread(self,app_fullname):
458+
"""Thread function to uninstall app"""
459+
print(f"Uninstalling app:{app_fullname}")
460+
try:
461+
PackageManager.uninstall_app(app_fullname)
462+
print(f"Successfully uninstalled{app_fullname}")
463+
# Note: The app list will be refreshed when launcher resumes
464+
exceptExceptionase:
465+
print(f"Error uninstalling{app_fullname}:{e}")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
fromPILimportImage,ImageDraw,ImageFont
3+
4+
defcreate_trashcan_icon():
5+
"""Create a 64x64 trashcan icon"""
6+
img=Image.new('RGBA', (64,64), (0,0,0,0))
7+
draw=ImageDraw.Draw(img)
8+
9+
# Draw trash can lid
10+
draw.rectangle([(16,18), (48,22)],fill='#E74C3C',outline='#C0392B',width=2)
11+
12+
# Draw trash can body (trapezoid shape)
13+
draw.polygon([(20,24), (44,24), (46,54), (18,54)],fill='#E74C3C',outline='#C0392B')
14+
15+
# Draw vertical lines on trash can body
16+
draw.line([(26,28), (24,50)],fill='#C0392B',width=2)
17+
draw.line([(32,28), (32,50)],fill='#C0392B',width=2)
18+
draw.line([(38,28), (40,50)],fill='#C0392B',width=2)
19+
20+
# Draw lid handle
21+
draw.arc([(26,12), (38,20)],start=0,end=180,fill='#C0392B',width=2)
22+
23+
# Save to res/mipmap-mdpi directory
24+
img.save('res/mipmap-mdpi/trashcan_icon.png','PNG',optimize=True)
25+
print("Trashcan icon saved as res/mipmap-mdpi/trashcan_icon.png")
26+
27+
defcreate_exit_icon():
28+
"""Create a 64x64 exit/close icon"""
29+
img=Image.new('RGBA', (64,64), (0,0,0,0))
30+
draw=ImageDraw.Draw(img)
31+
32+
# Draw circle background
33+
draw.ellipse([(12,12), (52,52)],fill='#3498DB',outline='#2980B9',width=2)
34+
35+
# Draw X (two diagonal lines)
36+
draw.line([(24,24), (40,40)],fill='#FFFFFF',width=4)
37+
draw.line([(40,24), (24,40)],fill='#FFFFFF',width=4)
38+
39+
# Save to res/mipmap-mdpi directory
40+
img.save('res/mipmap-mdpi/exit_icon.png','PNG',optimize=True)
41+
print("Exit icon saved as res/mipmap-mdpi/exit_icon.png")
42+
43+
if__name__=='__main__':
44+
create_trashcan_icon()
45+
create_exit_icon()
46+
print("All icons generated successfully!")
469 Bytes
Loading
274 Bytes
Loading

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp