// Copy into your package and list in manifest modules. Not native ui extensions.
namespace easy_ui {
// Comparisons also reject NaN/Infinity. Geometry is bounded before drawing.
float bounded(float value,float low,float high,float fallback) {
if(!(value>=-100000 && value<=100000))return fallback;
return value<low?low:(value>high?high:value);
}
// Variable-size flow: automatic wrapping and measured content height.
class Flow {
float x=0,y=0,width=240,gap=8,cursor=0,row=0,offset=0,placedWidth=0,placedHeight=0;
void begin(float left,float top,float span) {
x=bounded(left,-90000,90000,0);y=bounded(top,-90000,90000,0);
width=bounded(span,24,2048,240);gap=bounded(gap,0,32,8);
cursor=0;row=0;offset=0;
}
vec2 place(float span,float height) {
span=bounded(span,24,width,width);height=bounded(height,24,2048,32);
placedWidth=span;placedHeight=height;
if(cursor>0&&cursor+span>width){offset+=row+gap;cursor=0;row=0;}
vec2 position(x+cursor,y+offset);
cursor+=span+gap;if(height>row)row=height;
return position;
}
void newline(){if(row>0){offset+=row+gap;cursor=0;row=0;}}
float content_height() const{return offset+row;}
bool button(const string &in id,const string &in label,float span,float height=32){
vec2 p=place(span,height);return ui::button(id,label,p.x,p.y,placedWidth,placedHeight);
}
bool checkbox(const string &in id,const string &in label,bool value,float span,float height=32){
vec2 p=place(span,height);return ui::checkbox(id,label,value,p.x,p.y,placedWidth,placedHeight);
}
bool section(const string &in id,const string &in title,bool open){
newline();bool hit=button(id,(open?"- ":"+ ")+title,width);newline();
return hit?!open:open;
}
}
// Deterministic layout; no implicit clipping or native input hooks.
class Grid {
float x=0,y=0,width=240,rowHeight=32,gap=8;
uint columns=1,item=0;
void begin(float left,float top,float span,uint count,float height) {
x=bounded(left,-90000,90000,0);y=bounded(top,-90000,90000,0);
columns=count==0?1:(count>8?8:count);gap=bounded(gap,0,32,8);
float minimum=24*float(columns)+gap*float(columns-1);
width=bounded(span,minimum,2048,minimum);
rowHeight=bounded(height,24,128,32);item=0;
}
float cell_width() const { return (width-gap*float(columns-1))/float(columns); }
vec2 next() {uint index=item++;return vec2(x+float(index%columns)*(cell_width()+gap),y+float(index/columns)*(rowHeight+gap));}
}
// Virtualized list: only rows intersecting the viewport emit commands.
// No automatic persistence, global input or implicit native layout.
class ScrollList {
float offset=0,rowHeight=32,gap=8;
uint index=0;
bool draw(const string &in id,const array<string> &in choices,float x,float y,float width,float height) {
uint count=choices.length();
if(count==0 || count>256){offset=0;index=0;return false;}
if(index>=count)index=0;
x=bounded(x,-90000,90000,0);y=bounded(y,-90000,90000,0);
width=bounded(width,24,2048,240);height=bounded(height,24,2048,220);
rowHeight=bounded(rowHeight,24,128,32);gap=bounded(gap,0,32,8);
float stride=rowHeight+gap,content=float(count)*stride-gap;
offset=bounded(offset,0,content,0);
offset=ui::scroll(id+"-scroll",offset,content,x,y,width,height);
uint first=uint(offset/stride);
bool changed=false;
render::push_clip(x,y,width,height);
for(uint i=first;i<count;i++) {
float top=y+float(i)*stride-offset;
if(top>=y+height)break;
if(top+rowHeight<=y)continue;
if(ui::button(id+"-item-"+i,(i==index?"> ":" ")+choices[i],x,top,width,rowHeight)) {
changed=i!=index;index=i;
}
}
render::pop_clip();
return changed;
}
}
// Bounded visible rows with explicit navigation. This is pagination, not
// a native wheel-driven/clipped scroll container.
class PagedList {
uint page=0,index=0;
uint pages(uint count,uint rows) const {return rows==0||count==0?0:1+(count-1)/rows;}
void draw(const string &in id,const array<string> &in choices,uint rows,float x,float y,float width) {
rows=rows<1?1:(rows>8?8:rows);
uint total=pages(choices.length(),rows);
if(total==0){page=0;index=0;return;}
if(page>=total)page=total-1;
if(index>=choices.length())index=0;
// Handle navigation before drawing rows: no stale-page activations.
if(ui::button(id+"-prev","<",x,y,width/2-4,32)&&page>0)page--;
if(ui::button(id+"-next",">",x+width/2+4,y,width/2-4,32)&&page+1<total)page++;
uint begin=page*rows;
for(uint i=0;i<rows&&begin+i<choices.length();i++){
uint choice=begin+i;
if(ui::button(id+"-item-"+choice,(choice==index?"> ":" ")+choices[choice],x,y+36*float(i+1),width,32))index=choice;
}
}
}
class Column {
float x=0, y=0, width=240, gap=8;
void begin(float left,float top,float span) { x=left; y=top; width=span; }
vec2 next(float height) { vec2 p(x,y); y+=height+gap; return p; }
}
class Dropdown {
bool open=false;
uint index=0;
// Call header with ordinary controls, then popup LAST to overlay them.
void header(const string &in id,const array<string> &in choices,float x,float y,float width) {
if(choices.length()==0 || choices.length()>8) {open=false;return;}
if(index>=choices.length()) index=0;
if(ui::button(id,choices[index]+(open?" ^":" v"),x,y,width,32)) open=!open;
}
void popup(const string &in id,const array<string> &in choices,float x,float y,float width) {
if(!open || choices.length()==0 || choices.length()>8)return;
for(uint i=0;i<choices.length();i++) {
if(ui::button(id+"-"+i,choices[i],x,y+34*(i+1),width,32)) { index=i;open=false; }
}
}
}
class ColorPicker {
uint color=0xff994dff;
void draw(const string &in id,float x,float y,float width) {
float r=float((color>>24)&255),g=float((color>>16)&255),b=float((color>>8)&255),a=float(color&255);
r=ui::slider(id+"-r","Red",r,0,255,1,x,y,width,48);
g=ui::slider(id+"-g","Green",g,0,255,1,x,y+52,width,48);
b=ui::slider(id+"-b","Blue",b,0,255,1,x,y+104,width,48);
a=ui::slider(id+"-a","Alpha",a,0,255,1,x,y+156,width,48);
color=(uint(r)<<24)|(uint(g)<<16)|(uint(b)<<8)|uint(a);
render::rect(x,y+210,width,20,color,true);
}
}
bool section(const string &in id,const string &in title,bool expanded,float x,float y,float width) {
if(ui::button(id,(expanded?"- ":"+ ")+title,x,y,width,32)) return !expanded;
return expanded;
}
}
Easy Universal / Примеры и шаблоны