| 1 |
module test2; |
|---|
| 2 |
import qd, SDL_ttf; |
|---|
| 3 |
import tools.base, tools.functional, tools.stackthreads, tools.threadpool, std.string, std.file, tools.log: logln; |
|---|
| 4 |
import std.c.stdlib: exit; |
|---|
| 5 |
|
|---|
| 6 |
import std.random; |
|---|
| 7 |
import gcc.builtins, std.math:trunc, log, pow, PI, atan; |
|---|
| 8 |
real sqrtl(real r) { return __builtin_sqrtl(r); } |
|---|
| 9 |
float sqrtf(float f) { return __builtin_sqrtf(f); } |
|---|
| 10 |
double sqrt(double d) { return __builtin_sqrt(d); } |
|---|
| 11 |
|
|---|
| 12 |
float randf() { return (rand()*1f)/(typeof(rand()).max*1f); } |
|---|
| 13 |
real fract(real r) { if (r<0) return -r-trunc(-r); else return r-trunc(r); } |
|---|
| 14 |
|
|---|
| 15 |
template Pow(string what, int count) { |
|---|
| 16 |
static if (!count) const string Pow="typeof("~what~").init"; |
|---|
| 17 |
else static if (count==1) const string Pow=what; |
|---|
| 18 |
else const string Pow=what~"*"~Pow!(what, count-1); |
|---|
| 19 |
} |
|---|
| 20 |
|
|---|
| 21 |
real Ï; |
|---|
| 22 |
static this() { Ï=(1.0+sqrt(5.0))/2.0; } |
|---|
| 23 |
|
|---|
| 24 |
void delegate() idle; |
|---|
| 25 |
|
|---|
| 26 |
class Primitives { |
|---|
| 27 |
void drawFrame(ref Area orig) { |
|---|
| 28 |
with (orig) screen.With(orig) = line(0, 0, (size-pt(1, 1)).tupleof, Box=White); |
|---|
| 29 |
orig=orig.shrink(1); |
|---|
| 30 |
} |
|---|
| 31 |
TTF_FontClass font; |
|---|
| 32 |
this(string fn) { |
|---|
| 33 |
New(font, read(fn), 16); |
|---|
| 34 |
} |
|---|
| 35 |
} |
|---|
| 36 |
|
|---|
| 37 |
Primitives primitives; |
|---|
| 38 |
|
|---|
| 39 |
interface Widget { |
|---|
| 40 |
void draw(); |
|---|
| 41 |
void area(Area); Area area(); |
|---|
| 42 |
} |
|---|
| 43 |
|
|---|
| 44 |
class StandardWidget : Widget { |
|---|
| 45 |
protected Area myArea; |
|---|
| 46 |
abstract void draw(); |
|---|
| 47 |
void area(Area target) { myArea=target; } |
|---|
| 48 |
Area area() { return myArea; } |
|---|
| 49 |
this(Area _area) { area=_area; } |
|---|
| 50 |
this() { } |
|---|
| 51 |
} |
|---|
| 52 |
|
|---|
| 53 |
class StackThreadSched { |
|---|
| 54 |
Coroutine[] threads; |
|---|
| 55 |
void opCatAssign(Coroutine c) { threads ~= c; } |
|---|
| 56 |
void opCall() { foreach (thread; threads) thread(); } |
|---|
| 57 |
bool runsMe() { foreach (thread; threads) if (thread.runsMe) return true; return false; } |
|---|
| 58 |
} |
|---|
| 59 |
|
|---|
| 60 |
StackThreadSched mouse_sched; |
|---|
| 61 |
static this() { New(mouse_sched); } |
|---|
| 62 |
|
|---|
| 63 |
import std.thread, tools.threads; |
|---|
| 64 |
class FractalWidget(T=cfloat) : StandardWidget { |
|---|
| 65 |
static assert(is(T: creal)); |
|---|
| 66 |
Coroutine drawthr; |
|---|
| 67 |
Threadpool pool; |
|---|
| 68 |
T from, to; |
|---|
| 69 |
int iterations; |
|---|
| 70 |
enum DrawState { Reset, Stop } |
|---|
| 71 |
bool cancel; |
|---|
| 72 |
Area temp; |
|---|
| 73 |
this() { |
|---|
| 74 |
New(drawthr, &render); |
|---|
| 75 |
iterations = 16384; |
|---|
| 76 |
New(pool, 4); |
|---|
| 77 |
idle=&drawthr.opCall; |
|---|
| 78 |
New(temp); |
|---|
| 79 |
temp.tl=pt(0, 0); |
|---|
| 80 |
from=-2-2i; to=2+2i; |
|---|
| 81 |
super(); |
|---|
| 82 |
} |
|---|
| 83 |
void zoom(T nfrom, T nto) { |
|---|
| 84 |
from = nfrom; to = nto; |
|---|
| 85 |
cancel=true; while (cancel) drawthr(); New(drawthr, drawthr.dg); |
|---|
| 86 |
// auto backup = myArea; area = null; area = backup; |
|---|
| 87 |
} |
|---|
| 88 |
/*void handleMouse(proc yield) { |
|---|
| 89 |
enum State { start, inside, drag_zoomin, doZoomin, drag_zoomout, doZoomout }; |
|---|
| 90 |
pt start, end; |
|---|
| 91 |
void delegate(ref State)[State] tf=[ |
|---|
| 92 |
State.inside: (ref State s) { if (mouse.pressed) start=mouse.pos; }, |
|---|
| 93 |
State.drag_zoomin: (ref State s) { if (!mouse.pressed) end=mouse.pos; }, |
|---|
| 94 |
State.drag_zoomout: (ref State s) { if (!mouse.pressed) end=mouse.pos; }, |
|---|
| 95 |
State.doZoomin: (ref State s) { |
|---|
| 96 |
T nfrom=myArea.locate(start, from, to), nto=myArea.locate(end, from, to); |
|---|
| 97 |
zoom(nfrom, nto); |
|---|
| 98 |
logln("New range: ", from, " -> ", to); |
|---|
| 99 |
s=State.start; |
|---|
| 100 |
}, |
|---|
| 101 |
State.doZoomout: (ref State s) { |
|---|
| 102 |
T ifrom=myArea.locate(start, from, to), ito=myArea.locate(end, from, to); |
|---|
| 103 |
// determine transformation from new range to current range |
|---|
| 104 |
T scale=(to.re-from.re)/(ito.re-ifrom.re) + (to.im-from.im)/(ito.im-ifrom.im)*1i; |
|---|
| 105 |
T offs=(to.re+from.re-ito.re-ifrom.re)/2.0 + ((to.im+from.im-ito.im-ifrom.im)/2.0)*1i; |
|---|
| 106 |
offs=offs.re*scale.re + offs.im*scale.im*1i; |
|---|
| 107 |
// now apply transformation to current range |
|---|
| 108 |
T nfrom=(from.re+to.re - (to.re-from.re)*scale.re)/2.0 + ((from.im+to.im - (to.im-from.im)*scale.im)/2.0)*1i; |
|---|
| 109 |
T nto=(from.re+to.re + (to.re-from.re)*scale.re)/2.0 + ((from.im+to.im + (to.im-from.im)*scale.im)/2.0)*1i; |
|---|
| 110 |
zoom(nfrom+offs, nto+offs); |
|---|
| 111 |
s=State.start; |
|---|
| 112 |
} |
|---|
| 113 |
]; |
|---|
| 114 |
tf.addBidirectionalEdge({ return (mouse in area) && !mouse.pressed; }, { return !(mouse in area); }, State.start, State.inside); |
|---|
| 115 |
tf.addEdges( |
|---|
| 116 |
{ return mouse.pressed(Button.Left); }, State.inside, State.drag_zoomin, |
|---|
| 117 |
{ return mouse.pressed(Button.Right); }, State.inside, State.drag_zoomout, |
|---|
| 118 |
{ return !mouse.pressed; }, State.drag_zoomin, State.doZoomin, |
|---|
| 119 |
{ return !mouse.pressed; }, State.drag_zoomout, State.doZoomout |
|---|
| 120 |
); |
|---|
| 121 |
tf.runState(yield); |
|---|
| 122 |
}*/ |
|---|
| 123 |
alias void delegate(T p, void delegate(float)) func; |
|---|
| 124 |
void lyapunov(T p, void delegate(float) dg) { |
|---|
| 125 |
p*=2; // prescale |
|---|
| 126 |
string str="AAAAAAAAAABBAABB"; |
|---|
| 127 |
int limit=200; |
|---|
| 128 |
size_t which=0; |
|---|
| 129 |
float next_r() { |
|---|
| 130 |
auto ch=str[which++]; |
|---|
| 131 |
if (which>=str.length) which=0; |
|---|
| 132 |
if (ch=='A') return p.im; |
|---|
| 133 |
if (ch=='B') return p.re; |
|---|
| 134 |
asm { int 3; } |
|---|
| 135 |
} |
|---|
| 136 |
float sum=0; |
|---|
| 137 |
int i=0; |
|---|
| 138 |
float x=0.5; |
|---|
| 139 |
for (i=0; i<limit; ++i) { |
|---|
| 140 |
float r=next_r; |
|---|
| 141 |
x=x*r*(1f-x); |
|---|
| 142 |
if (i>limit/2) sum+=log(abs(r*(1f-2f*x))); |
|---|
| 143 |
if (sum>=float.max) break; // definitely outside |
|---|
| 144 |
} |
|---|
| 145 |
float f=sum/(limit/2f); |
|---|
| 146 |
if (f>0) dg(1); |
|---|
| 147 |
else dg(pow(atan(-f)/PI, cast(real)0.3f)); |
|---|
| 148 |
} |
|---|
| 149 |
void bailout_type(alias FN) (T p, void delegate(float) dg) { |
|---|
| 150 |
T c = 0+0i; |
|---|
| 151 |
static assert(is(ParameterTypeTuple!(FN) == Tuple!(T, T))); |
|---|
| 152 |
int i = iterations; |
|---|
| 153 |
for (; i>0; i--) { |
|---|
| 154 |
FN(c, p); |
|---|
| 155 |
if (c.re*c.re + c.im*c.im!<4) break; |
|---|
| 156 |
} |
|---|
| 157 |
if (i !> 0) dg(1); else { |
|---|
| 158 |
//auto smooth = (iterations - i + 1) - log(log(abs(pre_c)))/log(2); |
|---|
| 159 |
dg(i / 128f); |
|---|
| 160 |
} |
|---|
| 161 |
} |
|---|
| 162 |
void mandelbrot_sse(T[4] ps, void delegate(int, float) dg) { |
|---|
| 163 |
T[4] cs; |
|---|
| 164 |
foreach (ref c; cs) c = 0+0i; |
|---|
| 165 |
ubyte left = 15; // bit array |
|---|
| 166 |
int i = iterations; |
|---|
| 167 |
for (; i > 0; i--) { |
|---|
| 168 |
for (int k = 0; k < 4; ++k) { |
|---|
| 169 |
cs[k] *= cs[k]; cs[k] += ps[k]; |
|---|
| 170 |
} |
|---|
| 171 |
for (int k = 0; k < 4; ++k) { |
|---|
| 172 |
if (left & (1 << k)) { |
|---|
| 173 |
auto c = cs[k]; |
|---|
| 174 |
if (c.re*c.re + c.im * c.im !< 4) { |
|---|
| 175 |
left &= ~(1 << k); |
|---|
| 176 |
dg(k, i/128f); |
|---|
| 177 |
} |
|---|
| 178 |
} |
|---|
| 179 |
} |
|---|
| 180 |
if (!left) break; |
|---|
| 181 |
} |
|---|
| 182 |
if (!i) for (int k = 0; k < 4; ++k) if (left & (1 << k)) dg(k, 1); |
|---|
| 183 |
} |
|---|
| 184 |
final void mandelbrot_fn(ref T c, T p) { c *= c; c += p; } |
|---|
| 185 |
final void burning_ship_fn(ref T c, T p) { |
|---|
| 186 |
c = abs(c.re) + abs(c.im)*1.0i; |
|---|
| 187 |
c *= c; c += p; |
|---|
| 188 |
} |
|---|
| 189 |
alias bailout_type!(mandelbrot_fn) mandelbrot; |
|---|
| 190 |
alias bailout_type!(burning_ship_fn) burning_ship; |
|---|
| 191 |
void julia(T p, T c, void delegate(float) dg) { |
|---|
| 192 |
int limit=iterations; |
|---|
| 193 |
T x=p; |
|---|
| 194 |
int i=0; |
|---|
| 195 |
float sq=void; |
|---|
| 196 |
for (;i<limit; ++i) { |
|---|
| 197 |
x=x*x + c; |
|---|
| 198 |
sq=x.re*x.re+x.im*x.im; |
|---|
| 199 |
if (sq>=4) break; |
|---|
| 200 |
} |
|---|
| 201 |
dg((i-atan(sqrtf(sq)-2)/1.18)*0.1); |
|---|
| 202 |
} |
|---|
| 203 |
void render(proc yield) { |
|---|
| 204 |
logln("render proc - temp=", temp); |
|---|
| 205 |
assert(drawthr.runsMe()); |
|---|
| 206 |
size_t aa=1; |
|---|
| 207 |
float scale = 255f / (aa*aa); |
|---|
| 208 |
size_t cc=0; |
|---|
| 209 |
int count=0; |
|---|
| 210 |
void renderRange(int y1, int y2) { |
|---|
| 211 |
scope(exit) synchronized(this) --count; |
|---|
| 212 |
auto size = (to.re-from.re) + (to.im-from.im)*1.0i; |
|---|
| 213 |
for (int yc=y1; yc<y2; ++yc) { |
|---|
| 214 |
for (int xc=0; xc<temp.width; xc += 4) { |
|---|
| 215 |
if (cc) synchronized { cc--; return; } |
|---|
| 216 |
temp.pset(pt(xc, yc), 255, 255, 255); |
|---|
| 217 |
//float red=0f, green=0f, blue=0f; |
|---|
| 218 |
float[4] red=[0f, 0f, 0f, 0f], green=[0f, 0f, 0f, 0f], blue=[0f, 0f, 0f, 0f]; |
|---|
| 219 |
for (int xa=0; xa<aa; ++xa) for (int ya=0; ya<aa; ++ya) { |
|---|
| 220 |
T[4] start; |
|---|
| 221 |
for (int k = 0; k < 4; ++k) { |
|---|
| 222 |
auto mxc = xc + k; |
|---|
| 223 |
if (mxc !< temp.width) break; |
|---|
| 224 |
start[k]=(mxc*aa*1f + xa)/(temp.width*aa*1f) + (yc*aa*1f + ya)/(temp.height*aa*1f)*1i; |
|---|
| 225 |
start[k]=from.re+size.re*start[k].re - (from.im+size.im*start[k].im)*1.0i; |
|---|
| 226 |
} |
|---|
| 227 |
//lyapunov(start, (float t) { |
|---|
| 228 |
//mandelbrot(start, (float t) { |
|---|
| 229 |
mandelbrot_sse(start, (int k, float t) { |
|---|
| 230 |
//burning_ship(start, (float t) { |
|---|
| 231 |
//julia(start, -0.835-0.2321i, (float t) { |
|---|
| 232 |
green[k]+=fract(t*2); red[k]+=fract(t); blue[k]+=fract(t/2); |
|---|
| 233 |
}); |
|---|
| 234 |
} |
|---|
| 235 |
for (int k = 0; k < 4; ++k) { |
|---|
| 236 |
if (k !< temp.width) break; |
|---|
| 237 |
red[k]*=scale; green[k]*=scale; blue[k]*=scale; |
|---|
| 238 |
temp.pset(pt(xc+k, yc), cast(ubyte) red[k], cast(ubyte) green[k], cast(ubyte) blue[k]); |
|---|
| 239 |
} |
|---|
| 240 |
} |
|---|
| 241 |
} |
|---|
| 242 |
} |
|---|
| 243 |
for (int y=0; y<temp.height; y+=16) { |
|---|
| 244 |
pool.addTask("Worker "~.toString(y), &renderRange /fix/ stuple(y, min(y+16, temp.height))); |
|---|
| 245 |
synchronized(this) ++count; |
|---|
| 246 |
} |
|---|
| 247 |
while (true) { |
|---|
| 248 |
yield(); |
|---|
| 249 |
if (!count) if (temp.surface && filename.length) { |
|---|
| 250 |
SDL_SaveBMP(temp.surface, filename); |
|---|
| 251 |
//exit(0); |
|---|
| 252 |
filename = ""; |
|---|
| 253 |
} |
|---|
| 254 |
if (cancel) { |
|---|
| 255 |
cc=count; |
|---|
| 256 |
while (cc) yield(); |
|---|
| 257 |
cancel=false; |
|---|
| 258 |
yield(); |
|---|
| 259 |
} |
|---|
| 260 |
} |
|---|
| 261 |
} |
|---|
| 262 |
Area area() { return super.area; } |
|---|
| 263 |
void area(Area target) { |
|---|
| 264 |
if (target && (!myArea || target.dimensions!=myArea.dimensions)) { |
|---|
| 265 |
temp = screen.With(target.width, target.height) = cls; |
|---|
| 266 |
} |
|---|
| 267 |
super.area=target; |
|---|
| 268 |
} |
|---|
| 269 |
void draw() { |
|---|
| 270 |
myArea.blit(temp); |
|---|
| 271 |
} |
|---|
| 272 |
} |
|---|
| 273 |
|
|---|
| 274 |
template UseWidget(alias W) { |
|---|
| 275 |
private bool valid() { static if(isPointer!(typeof(W))) return W&&*W; else return W !is null; } |
|---|
| 276 |
void draw() { if (valid) W.draw(); } |
|---|
| 277 |
void area(Area target) { if (valid) W.area=target; } |
|---|
| 278 |
Area area() { if (valid) return W.area; return null; } |
|---|
| 279 |
} |
|---|
| 280 |
|
|---|
| 281 |
class GroupWidget : StandardWidget { |
|---|
| 282 |
mixin This!("super(myArea)"); |
|---|
| 283 |
Widget[] children; |
|---|
| 284 |
void opCatAssign(Widget[] w) { children~=w; } |
|---|
| 285 |
void opCatAssign(Widget w) { children~=w; } |
|---|
| 286 |
} |
|---|
| 287 |
|
|---|
| 288 |
class Draggable : Area { |
|---|
| 289 |
void delegate(ref pt)[] constraints; |
|---|
| 290 |
this(int x, int y, int w, int h) { tl=pt(x, y); dimensions=pt(w, h); } |
|---|
| 291 |
void set(Area area) { |
|---|
| 292 |
tl=area.tl; |
|---|
| 293 |
dimensions=area.dimensions; |
|---|
| 294 |
surface=area.surface; |
|---|
| 295 |
} |
|---|
| 296 |
void mousethr(proc yield) { |
|---|
| 297 |
enum State { start, inside, dragging } |
|---|
| 298 |
pt startpos, lastpos; |
|---|
| 299 |
void delegate(ref State)[State] transitions=[ |
|---|
| 300 |
State.inside: (ref State s) { lastpos=startpos=mouse.pos; }, |
|---|
| 301 |
State.dragging: (ref State s) { |
|---|
| 302 |
pt offset=mouse.pos-lastpos; |
|---|
| 303 |
lastpos=mouse.pos; |
|---|
| 304 |
tl+=offset; |
|---|
| 305 |
foreach (constraint; constraints) constraint(tl); |
|---|
| 306 |
} |
|---|
| 307 |
]; |
|---|
| 308 |
transitions.addBidirectionalEdge({ return mouse.pressed; }, State.inside, State.dragging); |
|---|
| 309 |
transitions.addEdges( |
|---|
| 310 |
{ return (mouse.pos in this)&&!mouse.pressed; }, State.start, State.inside, |
|---|
| 311 |
{ return !(mouse.pos in this); }, State.inside, State.start |
|---|
| 312 |
); |
|---|
| 313 |
transitions.runState(yield); |
|---|
| 314 |
} |
|---|
| 315 |
} |
|---|
| 316 |
|
|---|
| 317 |
class BaseSliderWidget : StandardWidget { |
|---|
| 318 |
float pos=0; |
|---|
| 319 |
invariant { assert(pos>=0f && pos<=1f); } |
|---|
| 320 |
void delegate(ref float) limit; |
|---|
| 321 |
Draggable box; |
|---|
| 322 |
void mousethr(proc yield) { box.mousethr({ update; yield(); }); } |
|---|
| 323 |
void update() { } |
|---|
| 324 |
void constrain(ref pt p) { |
|---|
| 325 |
p.y=myArea.tl.y+myArea.height/2; |
|---|
| 326 |
} |
|---|
| 327 |
void area(Area area) { |
|---|
| 328 |
super.area(area); |
|---|
| 329 |
auto new_box=area.width(cast(int)(area.width*(0.1+pos*0.9))).width(-area.width/10); |
|---|
| 330 |
if (!box) box=new Draggable(0, 0, 0, 0); |
|---|
| 331 |
box.set(new_box); |
|---|
| 332 |
} |
|---|
| 333 |
Area area() { return super.area(); } |
|---|
| 334 |
void draw() { |
|---|
| 335 |
with (area.select(pt(cast(int)(area.width*0.9), 3), Center)) line(tl.tupleof, br.tupleof, Box=White, Fill=Black); |
|---|
| 336 |
if (box) with (box) line(tl.tupleof, br.tupleof, Box=White, Fill=White~Black~Black); |
|---|
| 337 |
} |
|---|
| 338 |
} |
|---|
| 339 |
|
|---|
| 340 |
class SplitWidget(bool vert=true) : GroupWidget { |
|---|
| 341 |
int[] percentages; |
|---|
| 342 |
mixin This!("super(myArea)"); |
|---|
| 343 |
invariant { |
|---|
| 344 |
assert(percentages.length==children.length); |
|---|
| 345 |
assert(!percentages.length || (percentages /Reduce!("$+=$2") == 100)); |
|---|
| 346 |
} |
|---|
| 347 |
void opCatAssign(Widget[] w) { super~=w; percentages~=0; } |
|---|
| 348 |
void add(Widget w, int p) { super~=w; percentages~=p; } |
|---|
| 349 |
void area(Area target) { |
|---|
| 350 |
primitives.drawFrame(target); |
|---|
| 351 |
auto length=vert?target.height:target.length; |
|---|
| 352 |
super.area=target; |
|---|
| 353 |
if (!percentages.length) return; |
|---|
| 354 |
auto offsets=new int[percentages.length]; |
|---|
| 355 |
offsets[0]=0; |
|---|
| 356 |
foreach (id, ref entry; offsets[1..$]) entry=(percentages[id]*length)/100+offsets[id]; |
|---|
| 357 |
foreach (id, child; children) { |
|---|
| 358 |
static if (vert) child.area=target.select(0, offsets[id]).height((percentages[id]*length)/100); |
|---|
| 359 |
else child.area=target.select(offsets[id], 0).width((percentages[id]*.length)/100); |
|---|
| 360 |
} |
|---|
| 361 |
} |
|---|
| 362 |
void draw() { |
|---|
| 363 |
Area meep=target; primitives.drawFrame(meep); |
|---|
| 364 |
foreach (child; children) child.draw(); |
|---|
| 365 |
} |
|---|
| 366 |
} |
|---|
| 367 |
|
|---|
| 368 |
class FixedSplitWidget(bool vert=true) : Widget { |
|---|
| 369 |
Widget head, child; |
|---|
| 370 |
int offset; |
|---|
| 371 |
mixin This!("head, child, offset"); |
|---|
| 372 |
void draw() { head.draw; child.draw; } |
|---|
| 373 |
void area(Area target) { |
|---|
| 374 |
static if (vert) { |
|---|
| 375 |
head.area=target.height(offset); |
|---|
| 376 |
child.area=target.height(-target.height+offset+1); |
|---|
| 377 |
} else { |
|---|
| 378 |
head.area=target.width(offset); |
|---|
| 379 |
child.area=target.width(-target.width+offset+1); |
|---|
| 380 |
} |
|---|
| 381 |
} |
|---|
| 382 |
Area area() { return null; } |
|---|
| 383 |
} |
|---|
| 384 |
|
|---|
| 385 |
class Proxy : Widget { |
|---|
| 386 |
Widget *target; |
|---|
| 387 |
this(Widget *w) { target=w; } |
|---|
| 388 |
mixin UseWidget!(target); |
|---|
| 389 |
} |
|---|
| 390 |
|
|---|
| 391 |
class Frame : StandardWidget { |
|---|
| 392 |
Widget child; mixin This!("child=null"); |
|---|
| 393 |
void draw() { |
|---|
| 394 |
Area feep=myArea; |
|---|
| 395 |
primitives.drawFrame(feep); |
|---|
| 396 |
if (child) { |
|---|
| 397 |
child.area=feep.shrink(1); |
|---|
| 398 |
child.draw; |
|---|
| 399 |
} |
|---|
| 400 |
} |
|---|
| 401 |
} |
|---|
| 402 |
|
|---|
| 403 |
class AlignWidget : StandardWidget { |
|---|
| 404 |
Widget target; |
|---|
| 405 |
Align ment; |
|---|
| 406 |
this(Widget w, Align a) { target=w; ment=a; } |
|---|
| 407 |
void draw() { target.draw; } |
|---|
| 408 |
void area(Area a) { |
|---|
| 409 |
super.area=a; |
|---|
| 410 |
if (target) target.area=a.select(target.area.size, ment); // retarget |
|---|
| 411 |
} |
|---|
| 412 |
} |
|---|
| 413 |
|
|---|
| 414 |
class StackWidget : Widget { |
|---|
| 415 |
Widget[] targets; |
|---|
| 416 |
mixin This!("targets"); |
|---|
| 417 |
void draw() { foreach (target; targets) if (target) target.draw; } |
|---|
| 418 |
void area(Area a) { foreach (target; targets) if (target) target.area=a; } |
|---|
| 419 |
Area area() { return targets[0].area(); } |
|---|
| 420 |
} |
|---|
| 421 |
|
|---|
| 422 |
T[] array(T)(T[] elems...) { return elems.dup; } |
|---|
| 423 |
|
|---|
| 424 |
import tools.list; |
|---|
| 425 |
class WindowManager { |
|---|
| 426 |
|
|---|
| 427 |
class Window : Widget { |
|---|
| 428 |
private Widget frame, headline, box; |
|---|
| 429 |
Widget child; |
|---|
| 430 |
StackThreadSched motion; |
|---|
| 431 |
this(Area area=null, Widget ch=null) { |
|---|
| 432 |
headline=new TextBox("Window", Center|Left); |
|---|
| 433 |
box=new Frame(); box.area=Area(pt(0, 0), pt(10, 10)); |
|---|
| 434 |
this.child=ch; |
|---|
| 435 |
frame=new Frame(new StackWidget(array!(Widget)( |
|---|
| 436 |
new FixedSplitWidget!()(new Frame(headline), new Proxy(&child), 32), |
|---|
| 437 |
new AlignWidget(box, Bottom|Right) |
|---|
| 438 |
))); |
|---|
| 439 |
New(motion); |
|---|
| 440 |
if (area) frame.area=area; |
|---|
| 441 |
} |
|---|
| 442 |
mixin UseWidget!(frame); |
|---|
| 443 |
} |
|---|
| 444 |
|
|---|
| 445 |
list!(Window) windows; |
|---|
| 446 |
this() { mouse_sched ~= new Coroutine(&mousethr); } |
|---|
| 447 |
void draw() { windows.each((Window w) { w.draw(); }); } |
|---|
| 448 |
void mousethr(proc yield) { |
|---|
| 449 |
assert(mouse_sched.runsMe); |
|---|
| 450 |
enum State { start, title_inside, title_dragging, resize_inside, resize_dragging } |
|---|
| 451 |
pt lastpos; |
|---|
| 452 |
bool mouseInTitle() { with (windows) return length && top.headline.area && (mouse.pos in top.headline.area); } |
|---|
| 453 |
bool mouseInBox() { with (windows) return length && top.box.area && (mouse.pos in top.box.area); } |
|---|
| 454 |
void delegate(ref State)[State] transitions=[ |
|---|
| 455 |
State.start: (ref State s) { |
|---|
| 456 |
size_t newActive=size_t.max; |
|---|
| 457 |
windows.each_reverse((size_t id, ref Window w, proc brk) { |
|---|
| 458 |
if (mouse.pos in w.area) { |
|---|
| 459 |
if (mouse.pressed) { |
|---|
| 460 |
newActive=id; // make current |
|---|
| 461 |
if (s==State.start && (w is windows.top)) w.motion(); |
|---|
| 462 |
} else w.motion(); |
|---|
| 463 |
brk(); |
|---|
| 464 |
} |
|---|
| 465 |
}); |
|---|
| 466 |
if (newActive!=size_t.max && newActive!=windows.length-1) { |
|---|
| 467 |
windows.push_back(windows.take(newActive)); |
|---|
| 468 |
} |
|---|
| 469 |
}, |
|---|
| 470 |
State.title_inside: (ref State s) { lastpos=mouse.pos; }, |
|---|
| 471 |
State.title_dragging: (ref State s) { |
|---|
| 472 |
with (windows.top) area=area.move(mouse.pos-lastpos); |
|---|
| 473 |
lastpos=mouse.pos; |
|---|
| 474 |
}, |
|---|
| 475 |
State.resize_inside: (ref State s) { lastpos=mouse.pos; }, |
|---|
| 476 |
State.resize_dragging: (ref State s) { |
|---|
| 477 |
auto ns=mouse.pos-lastpos; |
|---|
| 478 |
with (windows.top) area=area.select(0, 0, area.width+ns.x, area.height+ns.y); |
|---|
| 479 |
lastpos=mouse.pos; |
|---|
| 480 |
} |
|---|
| 481 |
]; |
|---|
| 482 |
transitions.addBidirectionalEdges( |
|---|
| 483 |
{ return mouseInTitle && !mouse.pressed; }, { return !mouseInTitle; }, State.start, State.title_inside, |
|---|
| 484 |
{ return mouseInBox && !mouse.pressed; }, { return !mouseInBox; }, State.start, State.resize_inside, |
|---|
| 485 |
{ return mouse.pressed; }, State.title_inside, State.title_dragging, |
|---|
| 486 |
{ return mouse.pressed; }, State.resize_inside, State.resize_dragging |
|---|
| 487 |
); |
|---|
| 488 |
transitions.runState(yield); |
|---|
| 489 |
} |
|---|
| 490 |
} |
|---|
| 491 |
|
|---|
| 492 |
class Text : StandardWidget { |
|---|
| 493 |
Align alignment; |
|---|
| 494 |
this(string s, Align alignment=Left|Top) { text=primitives.font.render(s); this.alignment=alignment; } |
|---|
| 495 |
Area text; |
|---|
| 496 |
void draw() { myArea.select(text.size, alignment).blit(text); } |
|---|
| 497 |
} |
|---|
| 498 |
|
|---|
| 499 |
class TextBox : Text { |
|---|
| 500 |
this(string s, Align alignment=Left|Top) { super(s, alignment); } |
|---|
| 501 |
void draw() { screen.With(myArea) = cls; super.draw(); } |
|---|
| 502 |
} |
|---|
| 503 |
|
|---|
| 504 |
import std.c.time: time; |
|---|
| 505 |
void countFPS() { |
|---|
| 506 |
static int last_time; |
|---|
| 507 |
static int fps=0; |
|---|
| 508 |
auto now=time(null); |
|---|
| 509 |
if (last_time!=now) { |
|---|
| 510 |
logln(fps, " fps."); |
|---|
| 511 |
fps=0; |
|---|
| 512 |
last_time=now; |
|---|
| 513 |
} else ++fps; |
|---|
| 514 |
} |
|---|
| 515 |
|
|---|
| 516 |
real transfer(real a, real b, real a2, real b2, real t) { |
|---|
| 517 |
real fp=(a2-a)*(b-a)/(a2-a+b-b2); |
|---|
| 518 |
fp+=a; |
|---|
| 519 |
float fac1=(fp-a)/(fp-a2), fac2=(b-fp)/(b2-fp); |
|---|
| 520 |
assert(abs(fac1-fac2)<0.001, format("Inequality: ", fac1, " != ", fac2)); |
|---|
| 521 |
auto fac=fac1; |
|---|
| 522 |
auto steps=log(fac)/log(2.0); |
|---|
| 523 |
auto scale=pow(cast(real)2.0, steps*t); |
|---|
| 524 |
return fp-(fp-a)/scale; |
|---|
| 525 |
} |
|---|
| 526 |
creal transfer(creal a, creal b, creal a2, creal b2, real t) { |
|---|
| 527 |
return transfer(a.re, b.re, a2.re, b2.re, t)+transfer(a.im, b.im, a2.im, b2.im, t)*1.0i; |
|---|
| 528 |
} |
|---|
| 529 |
|
|---|
| 530 |
string filename; |
|---|
| 531 |
|
|---|
| 532 |
import tools.time: getTime=time; |
|---|
| 533 |
void main(string[] args) { |
|---|
| 534 |
New(primitives, "Vera.ttf"); |
|---|
| 535 |
auto wm=new WindowManager; |
|---|
| 536 |
auto f1=new FractalWidget!(creal); |
|---|
| 537 |
screen(700, 700, SDL_RESIZABLE); |
|---|
| 538 |
flip=off; |
|---|
| 539 |
auto w1=wm.new Window(display.select(10, 10, 260, 229), f1); |
|---|
| 540 |
wm.windows~=w1; |
|---|
| 541 |
//wm.windows~=wm.new Window(display.select(10, 10, 160, 60), new BaseSliderWidget); |
|---|
| 542 |
auto start_from = f1.from, start_to = f1.to; |
|---|
| 543 |
|
|---|
| 544 |
if (args.length>6) { |
|---|
| 545 |
creal nfrom=args[1].atof() + args[2].atof()*1.0i, nto=args[3].atof() + args[4].atof()*1.0i; |
|---|
| 546 |
//float step=args[5].atof()/args[6].atof(); |
|---|
| 547 |
int length = args[5].atoi(); |
|---|
| 548 |
void renderStep(int which) { |
|---|
| 549 |
real step = (1.0*which)/(1.0*length); |
|---|
| 550 |
auto nffrom=transfer(start_from, start_to, nfrom, nto, step); |
|---|
| 551 |
auto nfto=transfer(start_to, start_from, nto, nfrom, step); |
|---|
| 552 |
auto num = toString(which); |
|---|
| 553 |
auto limit = toString(length); |
|---|
| 554 |
while (num.length < limit.length) num = "0"~num; |
|---|
| 555 |
filename = args[6] ~ num ~ ".bmp"; |
|---|
| 556 |
if (filename.exists()) { logln("Skipping ", filename); return; } |
|---|
| 557 |
f1.zoom(nffrom, nfto); |
|---|
| 558 |
logln("new from ", nffrom, " to ", nfto, " step being ", step); |
|---|
| 559 |
while (filename.length) { cls(Black); wm.draw(); flip; idle(); events; countFPS(); } |
|---|
| 560 |
} |
|---|
| 561 |
for (int i = 0; i < length; ++i) renderStep(i); |
|---|
| 562 |
return; |
|---|
| 563 |
} |
|---|
| 564 |
|
|---|
| 565 |
long idletime=20; |
|---|
| 566 |
while (true) { |
|---|
| 567 |
cls(Black); |
|---|
| 568 |
wm.draw(); |
|---|
| 569 |
flip; |
|---|
| 570 |
idle(); |
|---|
| 571 |
events; |
|---|
| 572 |
countFPS(); |
|---|
| 573 |
mouse_sched (); |
|---|
| 574 |
} |
|---|
| 575 |
} |
|---|