001package polaris.sdk.ui.controls; 002import polaris.sdk.ui.*; 003 004import java.util.*; 005public class TreeView extends Widget { 006 private static final class Row{TreeNode node;int depth;Row(TreeNode n,int d){node=n;depth=d;}} 007 private final java.util.List<TreeNode> roots=new ArrayList<TreeNode>();private final java.util.List<Row> rows=new ArrayList<Row>(); 008 private TreeNode selected;private float scroll,shownScroll;private int selectedRow=-1; 009 public TreeView(){w=260;h=210;focusable=true;}public TreeView root(TreeNode node){if(node==null)throw new NullPointerException("node");roots.add(node);return this;} 010 public TreeNode selected(){return selected;}public int visibleRows(){flatten();return rows.size();} 011 private void flatten(){rows.clear();for(TreeNode n:roots)walk(n,0);selectedRow=-1;for(int i=0;i<rows.size();i++)if(rows.get(i).node==selected)selectedRow=i;scroll=Math.max(0,Math.min(Math.max(0,rows.size()*30-h+8),scroll));} 012 private void walk(TreeNode n,int depth){if(depth>128)throw new IllegalStateException("Tree nesting limit is 128");rows.add(new Row(n,depth));if(n.expanded())for(TreeNode c:n.nodes())walk(c,depth+1);} 013 private void choose(int i){if(i>=0&&i<rows.size()){selected=rows.get(i).node;selectedRow=i;float y=i*30;if(y<scroll)scroll=y;if(y+30>scroll+h-8)scroll=y+30-h+8;changed();}} 014 @Override protected void paint(Painter p){flatten();surface(p);pushContentClip(p);try{shownScroll=motion("scroll",scroll,28,.98f);p.clip(rect().inset(4).intersect(clip));if(selectedRow>=0){float sy=motion("selectedY",selectedRow*30,27,.82f);glassPart(p,ax+5,ay+4+sy-shownScroll,w-10,29,8,theme().selectedTint,theme().accent,.26f,theme().blur);}int first=Math.max(0,(int)(shownScroll/30));for(int i=first;i<rows.size();i++){Row row=rows.get(i);float y=ay+4+motion("row-"+System.identityHashCode(row.node),i*30,25,.96f)-shownScroll;if(y>ay+h)break;float x=ax+10+row.depth*17;if(row.node.nodes().isEmpty())p.fill(x+6,y+13,3,3,1.5f,theme().muted);else{float a=motion("arrow-"+System.identityHashCode(row.node),row.node.expanded()?1:0,25,.86f);float cx=x+9,cy=y+15;p.line(cx-3-a,cy-4+2*a,cx+2-2*a,cy+2*a,1.25f,theme().text);p.line(cx+2-2*a,cy+2*a,cx-3+7*a,cy+4-6*a,1.25f,theme().text);}p.text(row.node.text(),x+23,y+8,13,ink());}p.clip(clip);}finally{popContentClip(p);p.clip(clip);}} 015 @Override public void event(UiEvent e){flatten();if(e.type==UiEvent.Type.DOWN&&e.button==1){int i=(int)((e.y-ay-4+shownScroll)/30);if(i>=0&&i<rows.size()){choose(i);Row r=rows.get(i);if(e.x<ax+33+r.depth*17||e.clicks>=2)r.node.expanded(!r.node.expanded());}e.handled=true;}else if(e.type==UiEvent.Type.WHEEL){scroll=Math.max(0,Math.min(Math.max(0,rows.size()*30-h+8),scroll+e.wheel*60));e.handled=true;}else if(e.type==UiEvent.Type.KEY){if(e.key==38)choose(Math.max(0,selectedRow-1));else if(e.key==40)choose(Math.min(rows.size()-1,selectedRow+1));else if(selected!=null&&(e.key==39||e.key==37)){selected.expanded(e.key==39);changed();}else if(e.key==10&&selected!=null){selected.expanded(!selected.expanded());changed();}else return;e.handled=true;}} 016}