From 280958ada92db11c08556b44c6877f0ea5efb27d Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Wed, 12 Nov 2025 11:56:55 +0100 Subject: [PATCH] Add new app: Confetti --- .../META-INF/MANIFEST.JSON | 24 ++++ .../assets/confetti.py | 116 ++++++++++++++++++ .../res/drawable-mdpi/confetti1.png | Bin 0 -> 163 bytes .../res/drawable-mdpi/confetti2.png | Bin 0 -> 415 bytes .../res/drawable-mdpi/confetti3.png | Bin 0 -> 547 bytes .../res/mipmap-mdpi/icon_64x64.png | Bin 0 -> 2844 bytes 6 files changed, 140 insertions(+) create mode 100644 internal_filesystem/apps/com.micropythonos.confetti/META-INF/MANIFEST.JSON create mode 100644 internal_filesystem/apps/com.micropythonos.confetti/assets/confetti.py create mode 100644 internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti1.png create mode 100644 internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti2.png create mode 100644 internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti3.png create mode 100644 internal_filesystem/apps/com.micropythonos.confetti/res/mipmap-mdpi/icon_64x64.png diff --git a/internal_filesystem/apps/com.micropythonos.confetti/META-INF/MANIFEST.JSON b/internal_filesystem/apps/com.micropythonos.confetti/META-INF/MANIFEST.JSON new file mode 100644 index 00000000..2673966f --- /dev/null +++ b/internal_filesystem/apps/com.micropythonos.confetti/META-INF/MANIFEST.JSON @@ -0,0 +1,24 @@ +{ +"name": "Confetti", +"publisher": "MicroPythonOS", +"short_description": "Just shows confetti", +"long_description": "Nothing special, just a demo.", +"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.confetti/icons/com.micropythonos.confetti_0.0.1_64x64.png", +"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.confetti/mpks/com.micropythonos.confetti_0.0.1.mpk", +"fullname": "com.micropythonos.confetti", +"version": "0.0.1", +"category": "games", +"activities": [ + { + "entrypoint": "assets/confetti.py", + "classname": "Confetti", + "intent_filters": [ + { + "action": "main", + "category": "launcher" + } + ] + } + ] +} + diff --git a/internal_filesystem/apps/com.micropythonos.confetti/assets/confetti.py b/internal_filesystem/apps/com.micropythonos.confetti/assets/confetti.py new file mode 100644 index 00000000..3ebe15e5 --- /dev/null +++ b/internal_filesystem/apps/com.micropythonos.confetti/assets/confetti.py @@ -0,0 +1,116 @@ +import time +import random +import lvgl as lv + +from mpos.apps import Activity, Intent +import mpos.config +import mpos.ui + +class Confetti(Activity): + # === CONFIG === + SCREEN_WIDTH = 320 + SCREEN_HEIGHT = 240 + ASSET_PATH = "M:apps/com.micropythonos.confetti/res/drawable-mdpi/" + MAX_CONFETTI = 21 + GRAVITY = 100 # pixels/sec² + + def onCreate(self): + print("Confetti Activity starting...") + + # Background + self.screen = lv.obj() + self.screen.set_style_bg_color(lv.color_hex(0x000033), 0) # Dark blue + self.screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + self.screen.remove_flag(lv.obj.FLAG.SCROLLABLE) + + # Timing + self.last_time = time.ticks_ms() + + # Confetti state + self.confetti_pieces = [] + self.confetti_images = [] + self.used_img_indices = set() # Track which image slots are in use + + # Pre-create LVGL image objects + for i in range(self.MAX_CONFETTI): + img = lv.image(self.screen) + img.set_src(f"{self.ASSET_PATH}confetti{random.randint(1,3)}.png") + img.add_flag(lv.obj.FLAG.HIDDEN) + self.confetti_images.append(img) + + # Spawn initial confetti + for _ in range(self.MAX_CONFETTI): + self.spawn_confetti() + + self.setContentView(self.screen) + + def onResume(self, screen): + mpos.ui.th.add_event_cb(self.update_frame, 1) + + def onPause(self, screen): + mpos.ui.th.remove_event_cb(self.update_frame) + + def spawn_confetti(self): + """Safely spawn a new confetti piece with unique img_idx""" + # Find a free image slot + for idx, img in enumerate(self.confetti_images): + if img.has_flag(lv.obj.FLAG.HIDDEN) and idx not in self.used_img_indices: + break + else: + return # No free slot + + piece = { + 'img_idx': idx, + 'x': random.uniform(-10, self.SCREEN_WIDTH + 10), + 'y': random.uniform(50, 100), + 'vx': random.uniform(-100, 100), + 'vy': random.uniform(-250, -80), + 'spin': random.uniform(-400, 400), + 'age': 0.0, + 'lifetime': random.uniform(1.8, 5), + 'rotation': random.uniform(0, 360), + 'scale': 1.0 + } + self.confetti_pieces.append(piece) + self.used_img_indices.add(idx) + + def update_frame(self, a, b): + current_time = time.ticks_ms() + delta_ms = time.ticks_diff(current_time, self.last_time) + delta_time = delta_ms / 1000.0 + self.last_time = current_time + + new_pieces = [] + + for piece in self.confetti_pieces: + # === UPDATE PHYSICS === + piece['age'] += delta_time + piece['x'] += piece['vx'] * delta_time + piece['y'] += piece['vy'] * delta_time + piece['vy'] += self.GRAVITY * delta_time + piece['rotation'] += piece['spin'] * delta_time + piece['scale'] = max(0.3, 1.0 - (piece['age'] / piece['lifetime']) * 0.7) + + # === UPDATE LVGL IMAGE === + img = self.confetti_images[piece['img_idx']] + img.remove_flag(lv.obj.FLAG.HIDDEN) + img.set_pos(int(piece['x']), int(piece['y'])) + img.set_rotation(int(piece['rotation'] * 10)) # LVGL: 0.1 degrees + img.set_scale(int(256 * piece['scale']* 2)) # 256 = 100% + + # === CHECK IF DEAD === + off_screen = ( + piece['x'] < -60 or piece['x'] > self.SCREEN_WIDTH + 60 or + piece['y'] > self.SCREEN_HEIGHT + 60 + ) + too_old = piece['age'] > piece['lifetime'] + + if off_screen or too_old: + img.add_flag(lv.obj.FLAG.HIDDEN) + self.used_img_indices.discard(piece['img_idx']) + self.spawn_confetti() # Replace immediately + else: + new_pieces.append(piece) + + # === APPLY NEW LIST === + self.confetti_pieces = new_pieces diff --git a/internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti1.png b/internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti1.png new file mode 100644 index 0000000000000000000000000000000000000000..2a9639ec19425614e62271652c42acb6cb199cc5 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJo*pj^6T^Rm@;DWu&Co?cG zM0mP5hIn))CrGd^P7pCz#O?BDwbh^bjR)I&4)?7|$lB~Gmh2S%Wq#wqNiv5EUmo4i z(Ad~GGlqM^%cE(Gp$9)~d7#hwEb+zB4Gj|;A6o2O7QCojq^Id24@1;p9)X?vayKzB PFfe$!`njxgN@xNAOD{gt literal 0 HcmV?d00001 diff --git a/internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti2.png b/internal_filesystem/apps/com.micropythonos.confetti/res/drawable-mdpi/confetti2.png new file mode 100644 index 0000000000000000000000000000000000000000..7a7b65a3b6254a393b49110956674cddcce034b4 GIT binary patch literal 415 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJo*pj^6T^Rm@;DWu&Co?cG zhIqO-hIn))CrGe5bD6~Ore^PuI_1_SCl(~q{i*)+-}484%x`R5*dJlrb3|*-KcxVl z#^~bRy6FenC8R!@nR7^*F+BU2_}LRDPniGwU;n6CLVd=kBj#_u zEEoNM@!(7U)BpYJ9?5!5d|2D>Y$?9SqvFf!`{g{`b^m|=tbcfV$#UcQKZ_n3Gv^Cz zH2kY!`m}daYU+~%6Ot2Ck_{emPXG1!eY|1hy7~WZxL4ggy(Yi@|Nnypwmg4+|Ns8~ z)AXD6YIdKe9!}*u*Yj<9=fdu(os&ObXqT*zJ#^^E!E=9}*Gn@qJM)-Tu34FIN?6{T z&8=c+nx`pqX?`YQfvX~u^Q%_sVm68SibC&<6e;gVY&@IvpNF@u9&e015v zX^#_(_x|uR+?;1x?z8CB@_Qv*XMAe*USqha*Q^{HzJ=w6d#{$Y%aKW+FEc&MzOLVgAtba@q#@ik8 zRbP0l+XkK Dm8Jd| literal 0 HcmV?d00001 diff --git a/internal_filesystem/apps/com.micropythonos.confetti/res/mipmap-mdpi/icon_64x64.png b/internal_filesystem/apps/com.micropythonos.confetti/res/mipmap-mdpi/icon_64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..71203859895aae8a41f2384d759b17babf940d12 GIT binary patch literal 2844 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE^q_+G#Z??Z$2NFq9~9dt_PT7zt+40~SDD@YMMcfKx}J+z zHiUXkP@O2u$h$~X*)+LP>r^0PIg2WHqS5W&U;8ULjVI5XkUPnt{{Nmm)2rX#dH=oc zeg4jTw~Ma20kgh~B{Z0w`m#^`a) zmtEh!|L~t6bv3KWjavh+9}r4d_ws9P?En9n()&-bJ!4S6$zr>`C}#iK_8-C~Plz&&`7qBhXR^UZv(CsT5l(Zf99d5{V5WR5y%db9@ z%aw7gYZzZOd|Y;Y`_mo1z06B1}>V#SH-XTE~;MM*Cw<|jeAA;tWwUcEC<>>-B)YuWU7!@!IGEq;)uVM52JEd zSzKzTj?hGJoryOcPe&DBT*V)A{KYrp-@CNW8L!v3OLFgcm{nVt<5)7S_OH&-3hze6 z1F2 zc2K*u{l8aiq55Czzg!i^cAlK*eWY(~SKLfFzmunA?<-%9wa9%Op62v$)_!Fuh(F~znkLoF4_oa zE(rS)Hc>$1>}iH&H$@-+{Q8t}#cQ8@*9}t-q~Cb@^}&qePjkO9{!?Y=UZPeM)x4zS zsDr=d_g&!+!zLbiHn}0iPVwTr4Yt329$nNH_^P<)`h`w-7|Pe%8) zm_=8rO;Tm?`n-&3JEQ9XuFJ1i=QTgc zXPvk0bC*4{cM|t9gYQ zUn=8mR=QM>q&(kkL);3{ETxAx%$R%DYHoLKI~`%VdeJ4|ONhhDya9zoKFzwHLd#smZFaczyoGWPW@5_ifb@YxX^S7H#nrj^bsys@^?(Fx zawt@Lh~c_4e)^>WwULIN76qRSW43i&f6<9g_xFBUS~Q#;ZJJj*0%=XH{Fx2-}uDOeAs7we)Vsm4QBPaQj-~* zqO$^|5ACq=44LE5^z=phi^w;Viz}xUo>fx}m}P#=&g}9Vs|8oq39&PTGo&wH!JRfU z?*8SsvpCtfKA%5$wC~rxp!9{duiS$*?{xY$l?KH-_bgt|67A%)z~p^?+20Q{UVgnh z(?#}h!^;C13#2!&M;1u>5=U-8RJQ}%oGnPsWO z{B;61wOCoWK36Ah;&(mVB6HVwN|4LD)h(w~R4R%Twax!U=C7@t5)gPvdI$Fj#b56d z6`j@d{O2?Ayz60IKS%1+LdD%py$8QvypUJ8`0hK0UcO_O@6>r|Y~b8+vG4ok%gY)L zbZHl#466=&V)EhQ+0Io$Yt*wI6xcA{vc0(?&i(NI(05K|*0)1BS1mfC9QeR0VD$oz zHb0vwa!g(8<}|OAv$^G&C=(E&FPF(OFHY*0?Z-{i|FT^xp4rQuxBl$}KluRLj3uXy z*4S#Dzv%vYrLw8mIa{k^iMCG-SO0S`FOZduDzn}g=lAfm)y$UUUkutgf200fI$mY% zQL$G3rjQq}oWi0FX6X(JUG99ZB2spD={I-;I{SFv^Ey}BZ6uX? zr-=mKEqeb{E4(_^$tj^!^iv*3d3kvelMPqtMCM--8p^^y54voVevscP@GS&4Uz-nU9*#W>G6ammnVPVd2L-z^{CJ&`hE%{?Qiqbpo#uF5HAw4f_NUQXLuQ9PS>8RHF`)EXhg{f=U50wgJsy$B@!YR3-!eY%e2}?#&l>^t4U%3frCB1IF5EZho$FG6@avqwxUkdz c8~^feFVdQ&MBb@0HMQ6iU0rr literal 0 HcmV?d00001