001package polaris.sdk.ui.controls;
002import polaris.sdk.ui.*;
003
004import java.util.*;
005/** Mouse/keyboard popup menu with disabled items, separators, check items and submenus. */
006public class PopupMenu extends Widget {
007 public static final class Item {public String label;public Runnable action;public boolean enabled=true,checked,checkable,separator;public PopupMenu submenu;Item(String s,Runnable a){label=s;action=a;}}
008 protected final java.util.List<Item> entries=new ArrayList<Item>();protected int active=-1;private float scroll;
009 public PopupMenu(){w=240;h=8;focusable=true;}
010 public PopupMenu item(String name,Runnable action){entries.add(new Item(name,action));resize();return this;}
011 public PopupMenu disabled(String name){Item i=new Item(name,null);i.enabled=false;entries.add(i);resize();return this;}
012 public PopupMenu checked(String name,boolean value,Runnable action){Item i=new Item(name,action);i.checked=value;i.checkable=true;entries.add(i);resize();return this;}
013 public PopupMenu submenu(String name,PopupMenu menu){Item i=new Item(name,null);i.submenu=menu;entries.add(i);resize();return this;}
014 public PopupMenu separator(){Item i=new Item("",null);i.separator=true;i.enabled=false;entries.add(i);resize();return this;}
015 protected void resize(){h=Math.min(400,8+entries.size()*32);active=entries.isEmpty()?-1:0;}
016 public int itemCount(){return entries.size();}public void open(Ui context,float x,float y){context.popup(this,x,y);}
017 protected void choose(int index){if(index<0||index>=entries.size())return;Item item=entries.get(index);if(!item.enabled||item.separator)return;Ui context=ui;if(item.submenu!=null){context.popup(item.submenu,ax+w-3,ay+index*32);return;}if(item.checkable)item.checked=!item.checked;if(context!=null)context.close(this);if(item.action!=null)item.action.run();}
018 @Override protected void paint(Painter p){glassPart(p,ax,ay,w,h,12,.06f,theme().surface,0,theme().blur);p.clip(rect().inset(4).intersect(clip));int first=Math.max(0,(int)(scroll/32));if(active>=0&&active<entries.size()&&!entries.get(active).separator){float yy=ay+4+motion("activeY",active*32,30,.82f)-scroll;glassPart(p,ax+5,yy,w-10,30,8,theme().selectedTint,theme().accent,.28f,theme().blur);}for(int i=first;i<entries.size();i++){Item t=entries.get(i);float yy=ay+4+i*32-scroll;if(yy>=ay+h)break;if(t.separator){p.line(ax+14,yy+16,ax+w-14,yy+16,1,theme().border);continue;}p.text(t.checked?"✓":"",ax+12,yy+8,13,theme().text);p.text(t.label,ax+32,yy+8,13,t.enabled?ink():theme().muted);if(t.submenu!=null)p.text("›",ax+w-22,yy+6,16,theme().text);}p.clip(clip);}
019 @Override public void event(UiEvent e){if(e.type==UiEvent.Type.MOVE){active=Math.max(-1,Math.min(entries.size()-1,(int)((e.y-ay-4+scroll)/32)));}else if(e.type==UiEvent.Type.DOWN){active=(int)((e.y-ay-4+scroll)/32);pressed=true;}else if(e.type==UiEvent.Type.UP){if(pressed)choose(active);pressed=false;}else if(e.type==UiEvent.Type.WHEEL){scroll=Math.max(0,Math.min(Math.max(0,entries.size()*32+8-h),scroll+e.wheel*40));e.handled=true;}else if(e.type==UiEvent.Type.KEY){if(e.key==10||e.key==32){choose(active);e.handled=true;}else if(e.key==38||e.key==40){int direction=e.key==40?1:-1;for(int n=0;n<entries.size();n++){active=(active+direction+entries.size())%entries.size();if(entries.get(active).enabled)break;}scroll=Math.max(0,Math.min(Math.max(0,entries.size()*32+8-h),active*32-h/2));e.handled=true;}}}
020}