diff --git a/python_payload/application.py b/python_payload/application.py new file mode 100644 index 0000000000000000000000000000000000000000..f7bd5e7425c38b68a47d11df41786dc46f154647 --- /dev/null +++ b/python_payload/application.py @@ -0,0 +1,149 @@ +import ui +import event +import menu + +STATE_OFF = 0 +STATE_INIT = 10 +STATE_BACKGROUND = 200 +STATE_FOREGROUND = 300 +STATE_ERROR = 500 + + +class Application(): + def __init__(self,title="badge23 app", author="someone@earth"): + self.title = title + self.author = author + self.state = STATE_OFF + self.has_background = False + self.has_foreground = True + self._events_background = [] + self._events_foreground = [] + self.ui = ui.Viewport() + self.engine = event.the_engine + + self.add_event(event.Event( + name="exit", + action=self.exit, + condition=lambda e: e["type"]=="button" and e.get("from")==2 and e["change"] + )) + + self.ui.add(ui.Icon(label=self.title)) + + def __repr__(self): + return "App "+self.title + def init(self): + print("INIT") + self.state = STATE_INIT + print("before on_init") + self.on_init() + print("after on_init") + if self.has_background: + if self._events_background: + self._set_events(self._events_background,True) + engine.register_service_loop(self.main_always,True) + + + def run(self): + print ("RUN from",self.state) + if self.state == STATE_OFF: + print("go init") + self.init() + + if self.has_foreground: + self._to_foreground() + elif self.has_background: + self._to_background() + print ("App {} has no foreground, running in background".format(self.title)) + else: + print("App has neither foreground nor background, not doing anything") + + #start the eventloop if it is not already running + event.the_engine.eventloop() + + + def exit(self,data={}): + print("EXIT") + self.on_exit() + if self.state == STATE_FOREGROUND: + self._to_background() + + + def kill(self): + #disable all events + engine.register_service_loop(self.main_always,False) + engine.register_main_loop(self.main_forground,False) + self._set_events(self._events_background,False) + self._set_events(self._events_forground,False) + self.state = STATE_OFF + + def add_event(self,event,is_background=False): + if not is_background: + self._events_foreground.append(event) + else: + self._events_background.append(event) + + def is_foreground(self): + return self.state == STATE_FOREGROUND + + def _to_foreground(self): + print ("to foreground", self) + if not self.has_foreground: + #TODO log + return + + if self._events_background: + self._set_events(self_events_background,False) + self.state = STATE_FOREGROUND + + if self._events_foreground: + self._set_events(self._events_foreground,True) + #TODO should this really happen here?? + menu.menu_disable() + if self.ui: + self.ui.draw() + self.on_foreground() + + self.engine.register_main_loop(self.main_foreground,True) + + + def _to_background(self): + self.state = STATE_BACKGROUND + if self._events_foreground: + print("baz") + self._set_events(self._events_foreground,False) + + self.engine.register_main_loop(self.main_foreground,False) + if self.has_background: + self.state = STATE_BACKGROUND + self.on_background() + + def _set_events(self,events,enabled=True): + for e in events: + e.set_enabled(enabled) + + def on_init(self): + print("nothing to init") + pass + + def on_foreground(self): + pass + + def on_background(self): + pass + + def on_exit(self): + pass + + def on_kill(self): + pass + + def main_foreground(self): + #print("nothing") + pass + + def main_always(self): + pass + + + def main(self): + self.main_foreground() diff --git a/python_payload/cap_touch_demo.py b/python_payload/cap_touch_demo.py index 312b162c92e906a3ac55c1b5ec556818166707d7..6b64786a1127988e31e8a8655a16aab108b720ad 100644 --- a/python_payload/cap_touch_demo.py +++ b/python_payload/cap_touch_demo.py @@ -26,3 +26,8 @@ def run(): def foreground(): pass + +from application import Application + +app = Application("cap touch") +app.main_foreground = run diff --git a/python_payload/demo_menu.py b/python_payload/demo_menu.py index 522c758f31ffad47382a7724f33c354f5e113643..25c024a719f2422056e1b7d7d374e63c38f641d6 100644 --- a/python_payload/demo_menu.py +++ b/python_payload/demo_menu.py @@ -2,6 +2,7 @@ import menu import event import hardware import control +import application import demo_worms,demo_sparabo,cap_touch_demo, melodic_demo, harmonic_demo import menu_settings,menu_tinysynth @@ -9,67 +10,14 @@ import menu_settings,menu_tinysynth import time hardware.captouch_autocalib() +hardware.set_global_volume_dB(0) -def start_worms(action): - menu.menu_stack.append(menu.active_menu) - menu.active_menu=None - demo_worms.run() -def start_sparabo(action): - menu.menu_stack.append(menu.active_menu) - menu.active_menu=None - demo_sparabo.run() - -def start_captouch(action): - armed = False - while True: - cap_touch_demo.run() - time.sleep_ms(10) - if hardware.get_button(1) == 0: - armed = True - if armed and hardware.get_button(1) == 2: - break - -def start_melodic(action): - armed = False - melodic_demo.init() - hardware.set_global_volume_dB(20) - hardware.display_fill(0) - while True: - melodic_demo.run() - time.sleep_ms(10) - if hardware.get_button(1) == 0: - armed = True - if armed and hardware.get_button(1) == 2: - break - -def start_harmonic(action): - armed = False - harmonic_demo.init() - hardware.set_global_volume_dB(20) - hardware.display_fill(0) - while True: - harmonic_demo.run() - time.sleep_ms(10) - if hardware.get_button(1) == 0: - armed = True - if armed and hardware.get_button(1) == 2: - break menu_demo = menu.Menu("demo") -item_worms = menu.MenuItem("worms") -item_worms.action = start_worms -menu_demo.add(item_worms) - -item_abo = menu.MenuItem("abo") -item_abo.action = start_sparabo -menu_demo.add(item_abo) -item_cap = menu.MenuItem("captouch") -item_cap.action = start_captouch -menu_demo.add(item_cap) +for app_module in [demo_worms,demo_sparabo,cap_touch_demo,melodic_demo,harmonic_demo]: + menu_demo.add(menu.MenuItemApp(app_module.app)) -menu_demo.add(menu.MenuItem("melodic", action=start_melodic)) -menu_demo.add(menu.MenuItem("harmonic", action=start_harmonic)) testmenu = menu.Menu("test") diff --git a/python_payload/demo_sparabo.py b/python_payload/demo_sparabo.py index 4bbf4f1e76b93c902e971d677dd27eeba752b492..36ead3a4aabb26459618b6e9b0919fb77e3d113c 100644 --- a/python_payload/demo_sparabo.py +++ b/python_payload/demo_sparabo.py @@ -1,66 +1,58 @@ +#python +import math + +#badge23 import event import hardware from synth import tinysynth -import math +import application +import ui -def xy_from_polar(r,deg): - #rad = deg/180*math.pi - - return( ( - r * math.sin(deg), #x - r * math.cos(deg) #y - ) ) -ctx = hardware.get_ctx() popcorn = [9,7,9,5,0,5,-3,999] -synth = tinysynth(440,1) - -sequencer = None -handler = None def on_step(data): - note = popcorn[data["step"]] - if note != 999: - synth.tone(note) - synth.start() - - - ctx.rgb(1,1,0).rectangle(-120,-120,240,240).fill() - (x,y) = xy_from_polar(90,-2*math.pi/8*data["step"]+math.pi) - size=180 - ctx.rgb(0.8,0.8,0) - ctx.round_rectangle( - x-size/2, - y-size/2, - size,size,size//2 - ).fill() - ctx.move_to(x,y).rgb(0.5,0.5,0).text("{}".format(data["step"])) - hardware.display_update() - -def handle_input(data={}): - print("removed") - + ctx = app.ui.ctx + synth = app.synth + note = popcorn[data["step"]] + if note != 999: + synth.tone(note) + synth.start() - sequencer.remove() - ev.remove() + if not app.is_foreground(): return - #TODO this is a bad hack! - event.the_engine.events_timed=[] - - -def init(): ctx.text_align = ctx.CENTER ctx.text_baseline = ctx.MIDDLE - synth = tinysynth(440,1) - synth.decay(25) - global sequencer - sequencer = event.Sequence(bpm=160, steps=8, action=on_step, loop=True) - global ev - ev=event.Event(name="sparabo",action=handle_input, - condition=lambda e: e["type"] =="button" and e["change"] and e["value"] == 2) + ctx.rgb(1,1,0).rectangle(-120,-120,240,240).fill() + (x,y) = ui.xy_from_polar(90,-2*math.pi/8*data["step"]+math.pi) + size=180 + ctx.rgb(0.8,0.8,0) + ctx.round_rectangle( + x-size/2, + y-size/2, + size,size,size//2 + ).fill() + ctx.move_to(x,y).rgb(0.5,0.5,0).text("{}".format(data["step"])) + +class AppSparabo(application.Application): + def on_init(self): + hardware.set_global_volume_dB(0) + + self.synth = tinysynth(440,1) + self.synth.decay(25) + + print ("here") + self.sequencer = event.Sequence(bpm=160, steps=8, action=on_step, loop=True) + self.sequencer.start() + if self.sequencer.repeat_event: + self.add_event(self.sequencer.repeat_event) + + def on_foreground(self): + self.sequencer.start() -def run(): - init(); - print("run") - event.the_engine.eventloop() + def on_exit(self): + self.sequencer.stop() + + +app = AppSparabo("sequencer") \ No newline at end of file diff --git a/python_payload/demo_worms.py b/python_payload/demo_worms.py index ece958f380e652f236719ecef4089ec88b5b93ad..f20540cfc94bbf02ce0c7526582acc770a09d3e0 100644 --- a/python_payload/demo_worms.py +++ b/python_payload/demo_worms.py @@ -1,63 +1,115 @@ -import hardware +#python import random import time import math + +#badge23 import event +import application +import ui -def xy_from_polar(r,deg): - #rad = deg/180*math.pi +# Subclass Application +class AppWorms(application.Application): - return ( - r * math.sin(deg), #x - r * math.cos(deg) #y - ) + def on_init(self): + print("on init") -def randrgb(): - return ((random.random(),random.random(),random.random())) + # TODO(q3k): factor out frame limiter + self.last_render = None + self.target_fps = 30 + self.target_delta = 1000 / self.target_fps + self.frame_slack = None + self.last_report = None + self.worms = [] + for i in range(0): + worms.append(Worm()) + + def on_foreground(self): + print("on foreground") + ctx = app.ui.ctx + + #center the text horizontally and vertically + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + + #ctx.rgb() expects individual values for the channels, so unpack a list/tuple with * + #operations on ctx can be chained + #create a blue background + ctx.rgb(*ui.BLUE).rectangle(-ui.WIDTH/2,-ui.HEIGHT/2,ui.WIDTH,ui.HEIGHT).fill() + + #Write some text + ctx.move_to(0,0).rgb(*ui.WHITE).text("touch me :)") + + def main_foreground(self): + now = time.ticks_ms() + + if self.last_render is not None: + delta = now - self.last_render + if self.frame_slack is None: + self.frame_slack = self.target_delta - delta + if delta < self.target_delta: + return + + if self.last_report is None or (now - self.last_report) > 1000: + fps = 1000/delta + print(f'fps: {fps:.3}, frame budget slack: {self.frame_slack:.3}ms') + self.last_report = now + + # Simulation is currently locked to FPS. + + for w in self.worms: + w.draw() + w.move() + + self.last_render = now -WIDTH = 240 -HEIGHT = 240 +app = AppWorms("worms") -#Define a few RGB (0.0 to 1.0) colors -BLACK = (0,0,0) -RED = (1,0,0) -GREEN = (0,1,0) -BLUE = (0,0,1) -WHITE = (1,1,1) -GREY = (0.5,0.5,0.5) +def handle_input(data): + worms = app.worms + worms.append(Worm(data.get("index",0)*2*math.pi/10+math.pi )) + if len(worms)>10: + worms.pop(0) -# The global context (representing the whole screen) -ctx = None +app.add_event(event.Event( + name="worms_control", + action=handle_input, + condition=lambda data: data.get("type","")=="captouch" and data.get("value")==1 and data["change"], + ) +) -worms = [] +app.add_event(event.Event( + name="worms_exit", + action=app.exit, + condition=lambda e: e["type"]=="button" and e.get("from")==2 and e["change"] +)) class Worm(): def __init__(self,direction=None): - self.color = randrgb() - + self.color = ui.randrgb() + if direction: self.direction = direction else: self.direction = random.random()*math.pi*2 - self.size = 50 self.speed = self.size/5 - (x,y) = xy_from_polar(100, self.direction) + (x,y) = ui.xy_from_polar(100, self.direction) self.x = x self.y= y #(self.dx,self.dy) = xy_from_polar(1,self.direction) self._lastdist = 0.0 def draw(self): - ctx.rgb(*self.color) - ctx.round_rectangle( + app.ui.ctx.rgb(*self.color) + app.ui.ctx.round_rectangle( self.x-self.size/2, self.y-self.size/2, self.size,self.size,self.size//2 ).fill() - + def mutate(self): self.color = ([max(0,min(1,x+((random.random()-0.5)*0.3))) for x in self.color]) @@ -74,7 +126,7 @@ class Worm(): self.direction += (random.random()-0.5)*math.pi/4 - (dx,dy) = xy_from_polar(self.speed,self.direction) + (dx,dy) = ui.xy_from_polar(self.speed,self.direction) self.x+=dx self.y+=dy @@ -86,84 +138,11 @@ class Worm(): self.direction=-math.atan2(dy,dx) self.mutate() self._lastdist = dist - - -def handle_input(data): - worms.append(Worm(data.get("index",0)*2*math.pi/10+math.pi )) - if len(worms)>10: - worms.pop(0) - -def init(data={}): - # Get the global context (representing the whole screen) - ctx = hardware.get_ctx() - -# TODO(q3k): factor out frame limiter -last_render = None -target_fps = 30 -target_delta = 1000 / target_fps -frame_slack = None -last_report = None -def run(): - global last_render - global last_report - global frame_slack - now = time.ticks_ms() - - if last_render is not None: - delta = now - last_render - if frame_slack is None: - frame_slack = target_delta - delta - if delta < target_delta: - return - - if last_report is None or (now - last_report) > 1000: - fps = 1000/delta - print(f'fps: {fps:.3}, frame budget slack: {frame_slack:.3}ms') - last_report = now - - # Simulation is currently locked to FPS. - global worms - for w in worms: - w.draw() - w.move() - hardware.display_update() - last_render = now - - -def foreground(): - ctx.text_align = ctx.CENTER - ctx.text_baseline = ctx.MIDDLE - - #ctx.rgb() expects individual values for the channels, so unpack a list/tuple with * - #operations on ctx can be chained - #create a blue background - ctx.rgb(*BLUE).rectangle(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT).fill() - - #Write some text - ctx.move_to(0,0).rgb(*WHITE).text("touch me :)") - hardware.display_update() - global worms - worms = [] - for i in range(0): - worms.append(Worm()) - - event.Event(name="worms_control",action=handle_input, - condition=lambda data: data.get("type","")=="captouch" and data.get("value")==1 and data["change"]) - -def loop(data={}): - for w in worms: - w.draw() - w.move() - - hardware.display_update() - -def run(data={}): - init() - event.the_engine.userloop = loop - event.the_engine.eventloop() +# To run standalone: +#app.run() +#app.engine.eventloop() #Known problems: #ctx.rotate(math.pi) turns the display black until powercycled -#ctx.clip() causes crashes - +#ctx.clip() causes crashes \ No newline at end of file diff --git a/python_payload/event.py b/python_payload/event.py index 8c1d2e66a2c9bec2718163768b94da6c5c73a80b..599c8c80d849f862a924e3c2049185b77449ca33 100644 --- a/python_payload/event.py +++ b/python_payload/event.py @@ -37,12 +37,23 @@ class Engine(): self.remove_timed(group_id) def remove_timed(self,group_id): + #print("before:",len(self.events_timed)) self.events_timed = [event for event in self.events_timed if event.group_id!=group_id] self._sort_timed() + #print("after",len(self.events_timed)) def remove_input(self,group_id): self.events_input = [event for event in self.events_input if event.group_id!=group_id] + def register_main_loop(self,loop,enable=True): + if enable: + #print ("new userloop",loop) + #loop() + self.userloop = loop + elif self.userloop == loop: + #print ("removed userloop") + self.userloop = None + def _sort_timed(self): self.events_timed = sorted(self.events_timed, key = lambda event: event.deadline) @@ -102,7 +113,7 @@ class Engine(): entry["change"] = False #find and trigger the events q - triggered_events = list(filter(lambda e: e.condition(entry),self.events_input)) + triggered_events = list(filter(lambda e: e.enabled and e.condition(entry),self.events_input)) #print (triggered_events) #map(lambda e: e.trigger(d), triggered_events) for e in triggered_events: @@ -111,7 +122,8 @@ class Engine(): self.last_input_state=input_state def _handle_userloop(self): - if self.userloop: + if not self.userloop is None: + #print("userloop",self.userloop) self.userloop() def _eventloop_single(self): @@ -134,17 +146,21 @@ class Engine(): time.sleep(0.005) class Event(): - def __init__(self,name="unknown",data={},action=None,condition=None,group_id=None): + def __init__(self,name="unknown",data={},action=None,condition=None,group_id=None,enabled=False): #print (action) self.name = name self.eventtype = None self.data = data self.action = action self.condition = condition + self.enabled = enabled if not condition: self.condition = lambda x: True self.group_id=group_id - the_engine.add(self) + + if enabled: + self.set_enabled() + def trigger(self,triggerdata={}): @@ -153,11 +169,22 @@ class Event(): triggerdata.update(self.data) self.action(triggerdata) + def set_enabled(self,enabled=True): + self.enabled=enabled + if enabled: + the_engine.add(self) + else: + self.remove() + def remove(self): - if self in the_engine.events_input: + print ("remove",self) + while self in the_engine.events_input: + print ("from input") the_engine.events_input.remove(self) - if self in the_engine.events_timed: + while self in the_engine.events_timed: + print("from timed") the_engine.events_timed.remove(self) + the_engine._sort_timed() class EventTimed(Event): def __init__(self,ms,name="timer", *args, **kwargs): @@ -172,23 +199,48 @@ class EventTimed(Event): return ("event on tick {} ({})".format(self.deadline,self.name)) +#hack, make this oo +def on_restart(data): + print("loop sequence") + obj = data["object"] + if obj.is_running: + obj.start() + + + class Sequence(): def __init__(self,bpm=60,loop=True,steps=16,action=None): self.group_id = random.randint(0,100000000) self.bpm = bpm + self.steps = steps + self.repeat_event = None + self.loop = loop + self.events = [] + self.is_running = False + if not action: self.action = lambda data: print("step {}".format(data.get("step"))) else: self.action = action - stepsize_ms = int(60*1000/bpm) - for i in range(steps): - EventTimed(stepsize_ms*i,name="seq{}".format(i),action=self.action, data={'step':i}, group_id=self.group_id) - - if loop: - EventTimed(stepsize_ms*steps,name="loop", group_id=self.group_id, action=lambda data: Sequence(bpm=bpm,loop=loop,steps=steps,action=action)) - - def remove(self): - the_engine.remove_timed(self.group_id) + + def start(self): + if self.is_running: self.stop() + stepsize_ms = int(60*1000/self.bpm) + for i in range(self.steps): + print("adding sequence event ", i) + self.events.append(EventTimed(stepsize_ms*i,name="seq{}".format(i),action=self.action, data={'step':i}, group_id=self.group_id,enabled=True)) + if self.loop: + self.repeat_event=EventTimed(stepsize_ms*self.steps,name="loop", group_id=self.group_id, enabled=True, action=on_restart, data={"object":self}) + self.is_running=True + + def stop(self): + #for e in self.events: e.remove() + print("sequence stop") + the_engine.remove_timed(group_id=self.group_id) + self.events = [] + if self.repeat_event: self.repeat_event.remove() + self.is_running=False + global the_engine the_engine = Engine() \ No newline at end of file diff --git a/python_payload/harmonic_demo.py b/python_payload/harmonic_demo.py index 3207a09cd26f33fccfa514f2a6e899ae07bb52d1..95e73355e5525b444c7987b9b1c44546460c4a7b 100644 --- a/python_payload/harmonic_demo.py +++ b/python_payload/harmonic_demo.py @@ -58,3 +58,19 @@ def foreground(): tmp = chord_index chord_index = -1 set_chord(tmp) + +from application import Application +class HarmonicApp(Application): + def on_init(self): + init() + #foreground() + + def on_foreground(self): + #foreground() + pass + + def main_foreground(self): + print ("hererer") + run() + +app=HarmonicApp("harmonic") \ No newline at end of file diff --git a/python_payload/melodic_demo.py b/python_payload/melodic_demo.py index 8ffee2979dd158cbfcf233259d63686ca92316bb..06878ead5db0d7a18e69c16e2f81d44075a382e1 100644 --- a/python_payload/melodic_demo.py +++ b/python_payload/melodic_demo.py @@ -65,3 +65,17 @@ def init(): def foreground(): adjust_playing_field_to_octave() + +from application import Application + +class MelodicApp(Application): + def on_init(self): + init() + + def on_foreground(self): + foreground() + + def main_foreground(self): + run() + +app=MelodicApp("melodic") \ No newline at end of file diff --git a/python_payload/menu.py b/python_payload/menu.py index e4ec935f5610b0fa0ba480b15e379e4e11e5966c..885bb9f5de86dc9489bc9fac13716da16f336780 100644 --- a/python_payload/menu.py +++ b/python_payload/menu.py @@ -100,6 +100,15 @@ class MenuItem(): if self.action: self.action(data) +class MenuItemApp(MenuItem): + def __init__(self,app): + super().__init__(name=app.title) + self.target = app + + def enter(self,data={}): + if self.target: + self.target.run() + class MenuItemSubmenu(MenuItem): def __init__(self,submenu): super().__init__(name=submenu.name) @@ -168,37 +177,45 @@ def on_release(d): def on_enter(d): if active_menu is None: + + #TODO this should not bee needed... event.the_engine.userloop=None menu_back() return - else: + + if active_menu: active_menu.get_hovered_item().enter() + render() + else: + return + event.Event(name="menu rotation button",group_id="menu", - condition=lambda e: e["type"] =="button" and not e["change"] and abs(e["value"])==1 , - action=on_scroll + condition=lambda e: e["type"] =="button" and not e["change"] and abs(e["value"])==1, + action=on_scroll, enabled=True ) event.Event(name="menu rotation captouch",group_id="menu", condition=lambda e: e["type"] =="captouch" and not e["change"] and abs(e["value"])==1 and e["index"]==2, - action=on_scroll_captouch + action=on_scroll_captouch, enabled=False ) event.Event(name="menu rotation button release",group_id="menu", condition=lambda e: e["type"] =="button" and e["change"] and e["value"] ==0, - action=on_release + action=on_release, enabled=True ) event.Event(name="menu button enter",group_id="menu", - condition=lambda e: e["type"] =="button" and e["change"] and e["value"] == 2, - action=on_enter + condition=lambda e: e["type"] =="button" and e["change"] and e["from"] == 2, + action=on_enter, enabled=True ) def render(): print (active_menu) if active_menu is None: return - hardware.get_ctx().rectangle(-120,-120,240,240).rgb(0,0,0).fill() + + ui.the_ctx.rectangle(-120,-120,240,240).rgb(0,0,0).fill() active_menu.draw() #hardware.display_update() @@ -206,9 +223,18 @@ def set_active_menu(menu): global active_menu active_menu = menu +def menu_disable(): + global active_menu + if active_menu: + menu_stack.append(active_menu) + active_menu=None + def menu_back(): if not menu_stack: return previous = menu_stack.pop() - set_active_menu(previous) \ No newline at end of file + + set_active_menu(previous) + render() + \ No newline at end of file diff --git a/python_payload/ui.py b/python_payload/ui.py index c47d5baae43856a24e5c38b6498bc78d3e2567cc..3f8a79df7491c802ffe753ee88d7b8b74a72550a 100644 --- a/python_payload/ui.py +++ b/python_payload/ui.py @@ -4,30 +4,78 @@ import math import time from math import sin,cos,pi - +import gc + +WIDTH = 240 +HEIGHT = 240 + +#Define a few RGB (0.0 to 1.0) colors +BLACK = (0,0,0) +RED = (1,0,0) +GREEN = (0,1,0) +BLUE = (0,0,1) +WHITE = (1,1,1) +GREY = (0.5,0.5,0.5) GO_GREEN = (63/255,255/255,33/53) PUSH_RED = (251/255,72/255,196/255) +the_ctx = hardware.get_ctx() + +# Utility functions +def xy_from_polar(r,deg): + #rad = deg/180*math.pi + + return ( + r * math.sin(deg), #x + r * math.cos(deg) #y + ) + +#def ctx_circle(self, x,y, radius, arc_from = -math.pi, arc_to = math.pi): +# return self.arc(x,y,radius,arc_from,arc_to,True) + +#the_ctx.circle = ctx_circle + +def randrgb(): + return ((random.random(),random.random(),random.random())) class UIElement(): def __init__(self,origin=(0,0)): self.children = [] self.origin = origin - self.ctx = hardware.get_ctx() + self.ctx = the_ctx def draw(self, offset=(0,0)): pos = (self.origin[0]+offset[0],self.origin[1]+offset[1]) - self._draw(pos) + for child in self.children: child.draw(pos) + self._draw(pos) + def _draw(self,pos): pass def add(self, child): self.children.append(child) +class Viewport(UIElement): + def _draw(self,pos): + self.ctx.rgb(0.3,0.3,0.3).rectangle(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT).fill() + +class Circle(UIElement): + def __init__(self,radius,color=PUSH_RED,arc_from=-math.pi, arc_to=math.pi, *args, **kwargs): + self.radius = radius + self.color = color + self.arc_from = arc_from + self.arc_to = arc_to + super().__init__() + + def _draw(self,pos): + (x,y)=pos + self.ctx.move_to(x,y).rgb(*self.color).arc(x,y,self.radius,self.arc_from,self.arc_to,True).fill() + + class Text(UIElement): def __init__(self,s="foo"): self.s = s @@ -40,9 +88,7 @@ class Text(UIElement): class Icon(UIElement): def __init__(self,label="?", size=60): - self.bg_r = random.random() - self.bg_g = random.random() - self.bg_b = random.random() + self.bg = (random.random(),random.random(),random.random()) self.fg = 0 self.label=label self.size=size @@ -50,8 +96,8 @@ class Icon(UIElement): super().__init__() def _draw(self,pos): - x = int(pos[0]) - y = int(pos[1]) + #print("ui.Icon._draw()") + (x,y) = pos self.ctx.text_align = self.ctx.CENTER self.ctx.text_baseline = self.ctx.MIDDLE @@ -63,15 +109,23 @@ class Icon(UIElement): y-hs/2, hs,hs,hs//2 ).fill() - self.ctx.move_to(x,y).rgb(self.bg_r,self.bg_g,self.bg_b).arc(x,y,self.size/2,-math.pi,math.pi,True).fill() - #self.ctx.move_to(x,y-self.size/2).rgb(self.bg_r,self.bg_g,self.bg_b). - #.round_rectangle( - # x-self.size/2, - # y-self.size/2, - # self.size,self.size,self.size//2 - #).fill() + + self.ctx.move_to(x,y).rgb(*self.bg).arc(x,y,self.size/2,-math.pi,math.pi,True).fill() + #self.ctx.move_to(x,y).rgb(self.bg_r,self.bg_g,self.bg_b).circle(x,y,self.size/2).fill() + self.ctx.rgb(1,1,1).move_to(x,y).text(self.label) +class IconFlower(Icon): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.petal_size=size/2 + self.petal_count=5 + + def _draw(self,pos): + (x,y)=pos + + + class IconValue(Icon): def __init__(self, value=0, *args, **kwargs): super().__init__(*args, **kwargs) @@ -89,6 +143,7 @@ class IconValue(Icon): + class GroupStackedVertical(UIElement): pass @@ -104,10 +159,12 @@ class GroupRing(UIElement): pos = (self.origin[0]+offset[0],self.origin[1]+offset[1]) self._draw(pos) for index in range(len(self.children)): + #print("child",index) child = self.children[index] angle = 2*math.pi/len(self.children)*index+self.angle_offset x = math.sin(angle)*self.r+pos[0] y = -math.cos(angle)*self.r+pos[1] + #print("pos",(x,y)) child.draw(offset=(x,y)) def _draw(self,pos):