Skip to content
Snippets Groups Projects
Commit 9286291f authored by iggy's avatar iggy Committed by q3k
Browse files

menu: fundamentals and integration of demos; ui work; bugfixes

parent 7aa5d3d6
No related branches found
No related tags found
No related merge requests found
import menu
import event
import hardware
import demo_worms,demo_sparabo
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()
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)
testmenu = menu.Menu("test")
item_add = menu.MenuItem("+")
item_add.action = lambda x: testmenu.add(menu.MenuItem("new {}".format(len(testmenu.items))))
item_sub = menu.MenuItem("-")
item_sub.action = lambda x: testmenu.pop() if len(testmenu.items) > 4 else None
item_foo = menu.MenuItem("foo")
testmenu.add(item_foo)
testmenu.add(item_sub)
testmenu.add(item_add)
menu_main = menu.Menu("main",has_back=False)
menu_main.add(menu.MenuItemSubmenu(testmenu))
menu_main.add(menu.MenuItemSubmenu(menu_demo))
menu_main.add(menu.MenuItem("nix"))
menu.set_active_menu(menu_main)
menu.render()
event.the_engine.eventloop()
...@@ -12,13 +12,11 @@ def xy_from_polar(r,deg): ...@@ -12,13 +12,11 @@ def xy_from_polar(r,deg):
) ) ) )
ctx = hardware.get_ctx() ctx = hardware.get_ctx()
ctx.text_align = ctx.CENTER popcorn = [9,7,9,5,0,5,-3,999]
ctx.text_baseline = ctx.MIDDLE
synth = tinysynth(440,1) synth = tinysynth(440,1)
synth.decay(25)
popcorn = [9,7,9,5,0,5,-3,999] sequencer = None
handler = None
def on_step(data): def on_step(data):
note = popcorn[data["step"]] note = popcorn[data["step"]]
...@@ -39,8 +37,24 @@ def on_step(data): ...@@ -39,8 +37,24 @@ def on_step(data):
ctx.move_to(x,y).rgb(0.5,0.5,0).text("{}".format(data["step"])) ctx.move_to(x,y).rgb(0.5,0.5,0).text("{}".format(data["step"]))
hardware.display_update() hardware.display_update()
def handle_input(data={}):
sequencer.remove()
ev.remove()
event.Sequence(bpm=160, steps=8, action=on_step, loop=True)
event.the_engine.eventloop()
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)
def run():
init();
print("run")
event.the_engine.eventloop()
...@@ -30,16 +30,21 @@ GREY = (0.5,0.5,0.5) ...@@ -30,16 +30,21 @@ GREY = (0.5,0.5,0.5)
# The global context (representing the whole screen) # The global context (representing the whole screen)
ctx = None ctx = None
worms = None worms = []
class Worm(): class Worm():
def __init__(self): def __init__(self,direction=None):
self.color = randrgb() self.color = randrgb()
self.direction = random.random()*math.pi*2
self.size = 10 if direction:
self.direction = direction
else:
self.direction = random.random()*math.pi*2
self.size = 50
self.speed = self.size/5 self.speed = self.size/5
(x,y) = xy_from_polar(40, self.direction+90) (x,y) = xy_from_polar(100, self.direction)
self.x = x self.x = x
self.y= y self.y= y
#(self.dx,self.dy) = xy_from_polar(1,self.direction) #(self.dx,self.dy) = xy_from_polar(1,self.direction)
...@@ -59,7 +64,12 @@ class Worm(): ...@@ -59,7 +64,12 @@ class Worm():
def move(self): def move(self):
dist = math.sqrt(self.x**2+self.y**2) dist = math.sqrt(self.x**2+self.y**2)
self.size = (120-dist)/3 target_size = (130-dist)/3
if self.size>target_size: self.size-=1
if self.size<target_size: self.size+=1
self.speed = self.size/5 self.speed = self.size/5
self.direction += (random.random()-0.5)*math.pi/4 self.direction += (random.random()-0.5)*math.pi/4
...@@ -69,21 +79,23 @@ class Worm(): ...@@ -69,21 +79,23 @@ class Worm():
self.y+=dy self.y+=dy
if dist>110-self.size/2 and dist>self._lastdist: if dist>120-self.size/2 and dist>self._lastdist:
polar_position=math.atan2(self.y,self.x) polar_position=math.atan2(self.y,self.x)
dx=dx*-abs(math.cos(polar_position)) dx=dx*-abs(math.cos(polar_position))
dy=dy*-abs(math.sin(polar_position)) dy=dy*-abs(math.sin(polar_position))
self.direction=-math.atan2(dy,dx) self.direction=-math.atan2(dy,dx)
self.mutate() self.mutate()
self._lastdist = dist 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():
global worms def init(data={}):
global ctx # Get the global context (representing the whole screen)
worms = []
for i in range(23):
worms.append(Worm())
ctx = hardware.get_ctx() ctx = hardware.get_ctx()
# TODO(q3k): factor out frame limiter # TODO(q3k): factor out frame limiter
...@@ -129,8 +141,27 @@ def foreground(): ...@@ -129,8 +141,27 @@ def foreground():
ctx.rgb(*BLUE).rectangle(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT).fill() ctx.rgb(*BLUE).rectangle(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT).fill()
#Write some text #Write some text
ctx.move_to(0,0).rgb(*WHITE).text("Hi :)") ctx.move_to(0,0).rgb(*WHITE).text("touch me :)")
hardware.display_update() 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()
#Known problems: #Known problems:
#ctx.rotate(math.pi) turns the display black until powercycled #ctx.rotate(math.pi) turns the display black until powercycled
......
import hardware import hardware
import time import time
import math import math
import random
EVENTTYPE_TIMED = 1 EVENTTYPE_TIMED = 1
...@@ -10,152 +11,170 @@ EVENTTYPE_INPUT = 2 ...@@ -10,152 +11,170 @@ EVENTTYPE_INPUT = 2
#EVENTTYPE_CAPCROSS = 4 #EVENTTYPE_CAPCROSS = 4
class Engine(): class Engine():
def __init__(self): def __init__(self):
self.events_timed = [] self.events_timed = []
self.events_input = [] self.events_input = []
self.next_timed = None self.next_timed = None
self.last_input_state = None self.last_input_state = None
self.userloop = None
def add(self,event): self.is_running = False
if isinstance(event,EventTimed):
self.add_timed(event) def add(self,event):
elif isinstance(event,Event): if isinstance(event,EventTimed):
self.add_input(event) self.add_timed(event)
elif isinstance(event,Event):
def add_timed(self,event): self.add_input(event)
self.events_timed.append(event)
self.events_timed = sorted(self.events_timed, key = lambda event: event.deadline) def add_timed(self,event):
self.events_timed.append(event)
self._sort_timed()
def add_input(self,event):
self.events_input.append(event) def remove_timed(self,group_id):
self.events_timed = [event for event in self.events_timed if event.group_id==group_id]
def _handle_timed(self): self._sort_timed()
if not self.next_timed and self.events_timed:
self.next_timed = self.events_timed.pop(0) def _sort_timed(self):
self.events_timed = sorted(self.events_timed, key = lambda event: event.deadline)
now = time.ticks_ms()
def add_input(self,event):
if self.next_timed: self.events_input.append(event)
diff = time.ticks_diff(self.next_timed.deadline, now)
if diff <= 0: def _handle_timed(self):
self.next_timed.trigger({"ticks_ms":now, "ticks_delay": -diff}) if not self.next_timed and self.events_timed:
self.next_timed = None self.next_timed = self.events_timed.pop(0)
def _handle_input(self): now = time.ticks_ms()
input_state={
"b0":(hardware.get_button(0),"button",0), if self.next_timed:
"b1":(hardware.get_button(1),"button",1) diff = time.ticks_diff(self.next_timed.deadline, now)
} if diff <= 0:
self.next_timed.trigger({"ticks_ms":now, "ticks_delay": -diff})
for i in range(0,10): self.next_timed = None
input_state["c"+str(i)]=(hardware.get_captouch(i),"captouch",i)
def _handle_input(self):
input_state={
if not self.last_input_state: "b0":(hardware.get_button(0),"button",0),
self.last_input_state=input_state "b1":(hardware.get_button(1),"button",1)
return }
diff=[] for i in range(0,10):
for key in input_state: input_state["c"+str(i)]=(hardware.get_captouch(i),"captouch",i)
if input_state[key][0] != self.last_input_state[key][0]:
diff.append({
"type" : input_state[key][1], if not self.last_input_state:
"index" : input_state[key][2], self.last_input_state=input_state
"to" : input_state[key][0], return
"from" : self.last_input_state[key][0],
"ticks_ms": time.ticks_ms() diff=[]
}) for key in input_state:
if input_state[key][0] != self.last_input_state[key][0]:
diff.append({
if diff: "type" : input_state[key][1],
#print(diff) "index" : input_state[key][2],
for d in diff: "value" : input_state[key][0],
triggered_events = list(filter(lambda e: e.condition(d),self.events_input)) "from" : self.last_input_state[key][0],
#print (triggered_events) "ticks_ms": time.ticks_ms(),
#map(lambda e: e.trigger(d), triggered_events) "change": True
for e in triggered_events: })
e.trigger(d) else:
diff.append({
"type" : input_state[key][1],
self.last_input_state=input_state "index" : input_state[key][2],
"value" : input_state[key][0],
"ticks_ms": time.ticks_ms(),
"change": False
def _eventloop_single(self): })
self._handle_timed()
self._handle_input() if diff:
#print(diff)
def eventloop(self): for d in diff:
while True: triggered_events = list(filter(lambda e: e.condition(d),self.events_input))
self._eventloop_single() #print (triggered_events)
time.sleep(0.005) #map(lambda e: e.trigger(d), triggered_events)
for e in triggered_events:
e.trigger(d)
self.last_input_state=input_state
def _handle_userloop(self):
if self.userloop:
self.userloop()
def _eventloop_single(self):
self._handle_timed()
self._handle_input()
self._handle_userloop()
def eventloop(self):
if self.is_running:
print ("eventloop already running")
return
else:
print("eventloop started")
self.is_running=True
while self.is_running:
self._eventloop_single()
time.sleep(0.005)
class Event(): class Event():
def __init__(self,name="unknown",data={},action=None,condition=None): def __init__(self,name="unknown",data={},action=None,condition=None,group_id=None):
#print (action) #print (action)
self.name = name self.name = name
self.eventtype = None self.eventtype = None
self.data = data self.data = data
self.action = action self.action = action
self.condition = condition self.condition = condition
if not condition: if not condition:
self.condition = lambda x: True self.condition = lambda x: True
self.group_id=group_id
the_engine.add(self) the_engine.add(self)
#print (data)
def trigger(self,triggerdata={}):
def trigger(self,triggerdata={}): print ("triggered {} (with {})".format(self.name,triggerdata))
print ("triggered {} (with {})".format(self.name,triggerdata)) if not self.action is None:
if not self.action is None: triggerdata.update(self.data)
triggerdata.update(self.data) self.action(triggerdata)
#print("trigger")
self.action(triggerdata) def remove(self):
if self in the_engine.events_input:
the_engine.events_input.remove(self)
if self in the_engine.events_timed:
the_engine.events_timed.remove(self)
class EventTimed(Event): class EventTimed(Event):
def __init__(self,ms,name="timer", *args, **kwargs): def __init__(self,ms,name="timer", *args, **kwargs):
#super().__init__(name,data,action) #super().__init__(name,data,action)
self.deadline = time.ticks_add(time.ticks_ms(),ms) self.deadline = time.ticks_add(time.ticks_ms(),ms)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.name=name self.name=name
self.type=EVENTTYPE_TIMED self.type=EVENTTYPE_TIMED
def __repr__(self): def __repr__(self):
return ("event on tick {} ({})".format(self.deadline,self.name)) return ("event on tick {} ({})".format(self.deadline,self.name))
class Sequence(): class Sequence():
def __init__(self,bpm=60,loop=True,steps=16,action=None): def __init__(self,bpm=60,loop=True,steps=16,action=None):
self.bpm = bpm self.group_id = random.randint(0,100000000)
if not action: self.bpm = bpm
self.action = lambda data: print("step {}".format(data.get("step"))) if not action:
else: self.action = lambda data: print("step {}".format(data.get("step")))
self.action = action else:
stepsize_ms = int(60*1000/bpm) self.action = action
for i in range(steps): stepsize_ms = int(60*1000/bpm)
EventTimed(stepsize_ms*i,name="seq{}".format(i),action=self.action, data={'step':i}) 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",action=lambda data: Sequence(bpm=bpm,loop=loop,steps=steps,action=action)) 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)
global the_engine global the_engine
the_engine = Engine() the_engine = Engine()
\ No newline at end of file
EventTimed(200,name="bar",action=lambda data: print("GNANGNAGNA"))
EventTimed(100,name="foo")
Event(name="baz",
action=lambda data: print(data),
condition=lambda data: data.get('type')=="captouch"
)
#Sequence(action=axelf)
print (the_engine.events_timed)
#the_engine.eventloop()
import ui
import time
import hardware
import math
import event
menu_stack = []
active_menu = None
class Menu():
def __init__(self,name="menu",has_back=True):
self.name=name
self.items=[]
self.__index = 0
self.ui = ui.GroupRing(r=80)
self.ui.element_center = ui.Text(self.name)
self.icon = ui.Icon(label=name)
self.angle = 0
self.angle_step= 0.2
if has_back:
self.add(MenuItemBack())
def __repr__(self):
return "{} ({}): {}".format(self.name, self.__index, self.items)
def add(self, item):
self.items.append(item)
self.ui.add(item.ui)
def pop(self):
self.items.pop()
self.ui.children.pop()
def start(self):
print(self)
active_menu = self
render()
def scroll(self, n=0):
self.__index= (self.__index+n)%len(self.items)
return self.items[self.__index]
def rotate_by(self,angle):
self.rotate_to(self.angle+angle)
def rotate_to(self, angle):
self.angle = angle%(math.pi*2)
self.ui.angle_offset = self.angle
def rotate_steps(self, steps=1):
self.rotate_by(self.angle_step*steps)
def _get_hovered_index(self):
index = round(-self.angle/(math.pi*2)*len(self.items))
i = index%len(self.items)
return i
def get_hovered_item(self):
return self.items[self._get_hovered_index()]
def _get_angle_for_index(self,index):
return (math.pi*2/len(self.items)*(index)+self.angle)%(math.pi*2)
def _get_topness_for_index(self,index):
angle = self._get_angle_for_index(index)
dist = min(angle,math.pi*2-angle)
topness = 1-(dist/math.pi)
return topness
def draw(self):
hovered_index = self._get_hovered_index()
for i in range(len(self.items)):
item = self.items[i]
my_extra = abs(self._get_topness_for_index(i))*40
if i == hovered_index:
item.ui.has_highlight=True
my_extra+=20
else:
item.ui.has_highlight=False
item.ui.size=30+my_extra
self.ui.draw()
class MenuItem():
def __init__(self,name="item"):
self.name= name
self.action= None
self.ui = ui.Icon(label=name)
def __repr__(self):
return "item: {} (action: {})".format(self.name,"?")
def enter(self,data={}):
print("Enter MenuItem {}".format(self.name))
if self.action:
self.action(data)
class MenuItemSubmenu(MenuItem):
def __init__(self,submenu):
super().__init__(name=submenu.name)
self.ui = submenu.icon
self.target = submenu
def enter(self,data={}):
print("Enter Submenu {}".format(self.target.name))
menu_stack.append(active_menu)
set_active_menu(self.target)
class MenuItemBack(MenuItem):
def __init__(self):
super().__init__(name="<-")
def enter(self,data={}):
menu_back()
def on_scroll(d):
if active_menu is None:
return
if active_menu.angle_step<0.5:
active_menu.angle_step+=0.025
if d["value"] == -1:
active_menu.rotate_steps(-1)
elif d["value"] == 1:
active_menu.rotate_steps(1)
render()
def on_release(d):
if active_menu is None:
return
active_menu.angle_step = 0.2
render()
def on_enter(d):
if active_menu is None:
event.the_engine.userloop=None
menu_back()
return
else:
active_menu.get_hovered_item().enter()
event.Event(name="menu rotation",
condition=lambda e: e["type"] =="button" and not e["change"] and abs(e["value"])==1 ,
action=on_scroll
)
event.Event(name="menu rotation release",
condition=lambda e: e["type"] =="button" and e["change"] and e["value"] ==0,
action=on_release
)
event.Event(name="menu enter",
condition=lambda e: e["type"] =="button" and e["change"] and e["value"] == 2,
action=on_enter
)
def render():
print (active_menu)
if active_menu is None:
return
hardware.get_ctx().rectangle(-120,-120,240,240).rgb(0,0,0).fill()
active_menu.draw()
hardware.display_update()
def set_active_menu(menu):
global active_menu
active_menu = menu
def menu_back():
if not menu_stack:
return
previous = menu_stack.pop()
set_active_menu(previous)
\ No newline at end of file
import hardware
import random
import math
import time
class UIElement():
def __init__(self,origin=(0,0)):
self.children = []
self.origin = origin
self.ctx = hardware.get_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)
def _draw(self,pos):
#print(pos)
pass
def add(self, child):
self.children.append(child)
class Text(UIElement):
def __init__(self,s="foo"):
self.s = s
super().__init__()
def _draw(self, pos):
self.ctx.text_align = self.ctx.CENTER
self.ctx.text_baseline = self.ctx.MIDDLE
self.ctx.rgb(1,1,1).move_to(pos[0],pos[1]).text(self.s)
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.fg = 0
self.label=label
self.size = size
self.has_highlight = False
super().__init__()
def _draw(self,pos):
x = int(pos[0])
y = int(pos[1])
width = 55
height = 40
self.ctx.text_align = self.ctx.CENTER
self.ctx.text_baseline = self.ctx.MIDDLE
if self.has_highlight:
hs = self.size+5;
self.ctx.move_to(x,y-hs/2).rgb(1,1,1).round_rectangle(
x-hs/2,
y-hs/2,
hs,hs,hs//2
).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.rgb(1,1,1).move_to(x,y).text(self.label)
class GroupStackedVertical(UIElement):
pass
class GroupRing(UIElement):
def __init__(self,r=100,origin=(0,0),element_center=None):
self.r = r
self.angle_offset = 0
self.element_center=element_center
super().__init__(origin)
def draw(self, offset=(0,0)):
pos = (self.origin[0]+offset[0],self.origin[1]+offset[1])
self._draw(pos)
for index in range(len(self.children)):
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]
child.draw(offset=(x,y))
def _draw(self,pos):
if self.element_center:
self.element_center._draw(pos)
def test():
group = UIElement((10,0))
group.add(UIElement((10,10)))
group.add(UIElement((20,20)))
#group.draw()
ring = GroupRing(r=80)
ctx = hardware.get_ctx()
for i in range(12):
ring.add(Icon(str(i)))
hardware.display_update()
while True:
ctx.rectangle(0,0,240,240).rgb(1,1,1).fill()
ring.draw()
hardware.display_update()
ring.angle_offset+=2*math.pi/60
time.sleep(0.01)
hardware.display_update()
#test()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment