diff --git a/python_payload/demo_menu.py b/python_payload/demo_menu.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3c5e62590b9199133db1efb23cda1e6156f9ac2
--- /dev/null
+++ b/python_payload/demo_menu.py
@@ -0,0 +1,48 @@
+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()    
diff --git a/python_payload/demo_sparabo.py b/python_payload/demo_sparabo.py
index 691f26559df634cdc9a98d2975a32f66f471453d..50b6d465821a491425b2df0aa793d44951b00107 100644
--- a/python_payload/demo_sparabo.py
+++ b/python_payload/demo_sparabo.py
@@ -12,13 +12,11 @@ def xy_from_polar(r,deg):
 	)  )
 
 ctx = hardware.get_ctx()
-ctx.text_align = ctx.CENTER
-ctx.text_baseline = ctx.MIDDLE
-
+popcorn = [9,7,9,5,0,5,-3,999]
 synth = tinysynth(440,1)
-synth.decay(25)
 
-popcorn = [9,7,9,5,0,5,-3,999]
+sequencer = None
+handler = None
 
 def on_step(data):
 	note = popcorn[data["step"]]
@@ -39,8 +37,24 @@ def on_step(data):
 	ctx.move_to(x,y).rgb(0.5,0.5,0).text("{}".format(data["step"]))
 	hardware.display_update()
 	
-	
-	
-	
-event.Sequence(bpm=160, steps=8, action=on_step, loop=True)
-event.the_engine.eventloop()
+def handle_input(data={}):
+    sequencer.remove()
+    ev.remove()
+
+
+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()
diff --git a/python_payload/demo_worms.py b/python_payload/demo_worms.py
index 8784a3860b93cc9247c6ece29ea57c0e4fd3b760..ece958f380e652f236719ecef4089ec88b5b93ad 100644
--- a/python_payload/demo_worms.py
+++ b/python_payload/demo_worms.py
@@ -30,16 +30,21 @@ GREY = (0.5,0.5,0.5)
 # The global context (representing the whole screen)
 ctx = None
 
-worms = None
-
+worms = []
 
 class Worm():
-    def __init__(self):
+    def __init__(self,direction=None):
         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
-        (x,y) = xy_from_polar(40, self.direction+90)
+        (x,y) = xy_from_polar(100, self.direction)
         self.x = x
         self.y= y
         #(self.dx,self.dy) = xy_from_polar(1,self.direction)
@@ -59,7 +64,12 @@ class Worm():
     
     def move(self):
         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.direction += (random.random()-0.5)*math.pi/4
@@ -69,21 +79,23 @@ class Worm():
         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)
             dx=dx*-abs(math.cos(polar_position))
             dy=dy*-abs(math.sin(polar_position))
             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():
-    global worms
-    global ctx
-    worms = []
-    for i in range(23):
-        worms.append(Worm())
+
+def init(data={}):    
+    # Get the global context (representing the whole screen)
     ctx = hardware.get_ctx()
 
 # TODO(q3k): factor out frame limiter
@@ -129,8 +141,27 @@ def foreground():
     ctx.rgb(*BLUE).rectangle(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT).fill()
 
     #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()
+    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:
 #ctx.rotate(math.pi) turns the display black until powercycled
diff --git a/python_payload/event.py b/python_payload/event.py
index 3e8e9dac10407acd22fde36c9e350f7b44a80dd9..4c176ae308a84b5ee6a473b1d0904848801c7b67 100644
--- a/python_payload/event.py
+++ b/python_payload/event.py
@@ -1,6 +1,7 @@
 import hardware
 import time 
 import math
+import random
 
 
 EVENTTYPE_TIMED = 1
@@ -10,152 +11,170 @@ EVENTTYPE_INPUT = 2
 #EVENTTYPE_CAPCROSS = 4
 
 class Engine():
-	def __init__(self):
-		self.events_timed = []
-		self.events_input = []
-		self.next_timed = None
-		self.last_input_state = None
-	
-	def add(self,event):
-		if isinstance(event,EventTimed):
-			self.add_timed(event)
-		elif isinstance(event,Event):
-			self.add_input(event)
-			
-	def add_timed(self,event):
-		self.events_timed.append(event)
-		self.events_timed = sorted(self.events_timed, key = lambda event: event.deadline)
-	
-	
-	def add_input(self,event):
-	    self.events_input.append(event)
-	
-	def _handle_timed(self):
-		if not self.next_timed and self.events_timed:
-			self.next_timed = self.events_timed.pop(0)
-	
-		now = time.ticks_ms()
-		
-		if self.next_timed:
-			diff = time.ticks_diff(self.next_timed.deadline, now)
-			if diff <= 0:
-				self.next_timed.trigger({"ticks_ms":now, "ticks_delay": -diff})
-				self.next_timed = None
-				
-	def _handle_input(self):
-		input_state={
-			"b0":(hardware.get_button(0),"button",0),
-			"b1":(hardware.get_button(1),"button",1)
-		}
-		
-		for i in range(0,10):
-			input_state["c"+str(i)]=(hardware.get_captouch(i),"captouch",i)
-			
-		
-		if not self.last_input_state:
-			self.last_input_state=input_state
-			return
-		
-		diff=[]
-		for key in input_state:
-			if input_state[key][0] != self.last_input_state[key][0]:
-				diff.append({
-					"type" : input_state[key][1],
-					"index" : input_state[key][2],
-					"to" : input_state[key][0],
-					"from" : self.last_input_state[key][0],
-					"ticks_ms": time.ticks_ms()
-				})
-				
-		
-		if diff:
-			#print(diff)
-			for d in diff:
-				triggered_events = list(filter(lambda e: e.condition(d),self.events_input))
-				#print (triggered_events)
-				#map(lambda e: e.trigger(d), triggered_events)
-				for e in triggered_events:
-					e.trigger(d)
-					
-			
-		self.last_input_state=input_state		
-		
-		
-		
-	def _eventloop_single(self):
-		self._handle_timed()
-		self._handle_input()
-			
-	def eventloop(self):
-		while True:
-			self._eventloop_single()
-			time.sleep(0.005)
-		
+    def __init__(self):
+        self.events_timed = []
+        self.events_input = []
+        self.next_timed = None
+        self.last_input_state = None
+        self.userloop = None
+        self.is_running = False
+    
+    def add(self,event):
+        if isinstance(event,EventTimed):
+            self.add_timed(event)
+        elif isinstance(event,Event):
+            self.add_input(event)
+            
+    def add_timed(self,event):
+        self.events_timed.append(event)
+        self._sort_timed()
+    
+    def remove_timed(self,group_id):
+        self.events_timed = [event for event in self.events_timed if event.group_id==group_id]
+        self._sort_timed()
+    
+    def _sort_timed(self):
+        self.events_timed = sorted(self.events_timed, key = lambda event: event.deadline)
+
+    def add_input(self,event):
+        self.events_input.append(event)
+    
+    def _handle_timed(self):
+        if not self.next_timed and self.events_timed:
+            self.next_timed = self.events_timed.pop(0)
+    
+        now = time.ticks_ms()
+        
+        if self.next_timed:
+            diff = time.ticks_diff(self.next_timed.deadline, now)
+            if diff <= 0:
+                self.next_timed.trigger({"ticks_ms":now, "ticks_delay": -diff})
+                self.next_timed = None
+                
+    def _handle_input(self):
+        input_state={
+            "b0":(hardware.get_button(0),"button",0),
+            "b1":(hardware.get_button(1),"button",1)
+        }
+        
+        for i in range(0,10):
+            input_state["c"+str(i)]=(hardware.get_captouch(i),"captouch",i)
+            
+        
+        if not self.last_input_state:
+            self.last_input_state=input_state
+            return
+        
+        diff=[]
+        for key in input_state:
+            if input_state[key][0] != self.last_input_state[key][0]:
+                diff.append({
+                    "type" : input_state[key][1],
+                    "index" : input_state[key][2],
+                    "value" : input_state[key][0],
+                    "from" : self.last_input_state[key][0],
+                    "ticks_ms": time.ticks_ms(),
+                    "change": True
+                })
+            else:
+                 diff.append({
+                    "type" : input_state[key][1],
+                    "index" : input_state[key][2],
+                    "value" : input_state[key][0],
+                    "ticks_ms": time.ticks_ms(),
+                    "change": False
+                })
+    
+        if diff:
+            #print(diff)
+            for d in diff:
+                triggered_events = list(filter(lambda e: e.condition(d),self.events_input))
+                #print (triggered_events)
+                #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():
-	def __init__(self,name="unknown",data={},action=None,condition=None):
-		#print (action)
-		self.name = name
-		self.eventtype = None
-		self.data = data
-		self.action = action
-		self.condition = condition
-		if not condition:
-			self.condition = lambda x: True
-		
-		the_engine.add(self)
-			
-		#print (data)
-		
-	def trigger(self,triggerdata={}):
-		print ("triggered {} (with {})".format(self.name,triggerdata))
-		if not self.action is None:
-			triggerdata.update(self.data)
-			#print("trigger")
-			self.action(triggerdata)
-	
+    def __init__(self,name="unknown",data={},action=None,condition=None,group_id=None):
+        #print (action)
+        self.name = name
+        self.eventtype = None
+        self.data = data
+        self.action = action
+        self.condition = condition
+        if not condition:
+            self.condition = lambda x: True
+        self.group_id=group_id
+        the_engine.add(self)
+            
+        
+    def trigger(self,triggerdata={}):
+        print ("triggered {} (with {})".format(self.name,triggerdata))
+        if not self.action is None:
+            triggerdata.update(self.data)
+            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):
-	def __init__(self,ms,name="timer", *args, **kwargs):
-		#super().__init__(name,data,action)
-		self.deadline = time.ticks_add(time.ticks_ms(),ms)
-		
-		super().__init__(*args, **kwargs)
-		self.name=name
-		self.type=EVENTTYPE_TIMED
-		
-	def __repr__(self):
-		return ("event on tick {} ({})".format(self.deadline,self.name))
+    def __init__(self,ms,name="timer", *args, **kwargs):
+        #super().__init__(name,data,action)
+        self.deadline = time.ticks_add(time.ticks_ms(),ms)
+        
+        super().__init__(*args, **kwargs)
+        self.name=name
+        self.type=EVENTTYPE_TIMED
+        
+    def __repr__(self):
+        return ("event on tick {} ({})".format(self.deadline,self.name))
 
 
 class Sequence():
-	def __init__(self,bpm=60,loop=True,steps=16,action=None):
-		self.bpm = bpm
-		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})
-		
-		if loop:
-			EventTimed(stepsize_ms*steps,name="loop",action=lambda data: Sequence(bpm=bpm,loop=loop,steps=steps,action=action))
+    def __init__(self,bpm=60,loop=True,steps=16,action=None):
+        self.group_id = random.randint(0,100000000)
+        self.bpm = bpm
+        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)
+        
 global the_engine
-the_engine = Engine()
-
-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()
-
+the_engine = Engine()
\ No newline at end of file
diff --git a/python_payload/menu.py b/python_payload/menu.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e44a2b7d7088a23808918ef0e97a6d5363fd0cf
--- /dev/null
+++ b/python_payload/menu.py
@@ -0,0 +1,180 @@
+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
diff --git a/python_payload/ui.py b/python_payload/ui.py
new file mode 100644
index 0000000000000000000000000000000000000000..48fb0eed34f2e8cc036e8be73a9ffef82c0ee226
--- /dev/null
+++ b/python_payload/ui.py
@@ -0,0 +1,112 @@
+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()