| 1 |
module test24; |
|---|
| 2 |
|
|---|
| 3 |
import qd, SDL_image; |
|---|
| 4 |
import tools.log; |
|---|
| 5 |
|
|---|
| 6 |
interface Draggable { |
|---|
| 7 |
void pickUp(pt); |
|---|
| 8 |
void drag(pt); |
|---|
| 9 |
void drop(pt); |
|---|
| 10 |
void setDragDg(void delegate(ref proc)); |
|---|
| 11 |
} |
|---|
| 12 |
|
|---|
| 13 |
interface Drawable { |
|---|
| 14 |
void draw(); |
|---|
| 15 |
} |
|---|
| 16 |
|
|---|
| 17 |
interface DropTarget { |
|---|
| 18 |
bool accept(Draggable droppings, ref pt where); |
|---|
| 19 |
} |
|---|
| 20 |
|
|---|
| 21 |
struct DragDropFuns(DragDropDesc) { |
|---|
| 22 |
static { |
|---|
| 23 |
bool delegate(pt)[Draggable] drags; |
|---|
| 24 |
Draggable selected; |
|---|
| 25 |
|
|---|
| 26 |
void draw() { |
|---|
| 27 |
if (auto draw = cast(Drawable) selected) |
|---|
| 28 |
draw.draw(); |
|---|
| 29 |
} |
|---|
| 30 |
void checkEvents() { |
|---|
| 31 |
if (selected) { // currently dragging |
|---|
| 32 |
if (DragDropDesc.stopDrag()) { |
|---|
| 33 |
selected.drop(mouse.pos); |
|---|
| 34 |
selected = null; |
|---|
| 35 |
} else if (mouse.moved) { |
|---|
| 36 |
selected.drag(mouse.pos); |
|---|
| 37 |
} |
|---|
| 38 |
} else { // not currently dragging and not currently clicking |
|---|
| 39 |
if (DragDropDesc.startDrag()) { |
|---|
| 40 |
Draggable target; |
|---|
| 41 |
foreach (drag, dg; drags) { |
|---|
| 42 |
if (dg(mouse.pos)) { |
|---|
| 43 |
target = drag; |
|---|
| 44 |
break; |
|---|
| 45 |
} |
|---|
| 46 |
} |
|---|
| 47 |
if (!target) return; |
|---|
| 48 |
drags.remove(target); |
|---|
| 49 |
selected = target; |
|---|
| 50 |
selected.pickUp(mouse.pos); |
|---|
| 51 |
} |
|---|
| 52 |
} |
|---|
| 53 |
} |
|---|
| 54 |
} |
|---|
| 55 |
} |
|---|
| 56 |
|
|---|
| 57 |
struct DropTargets { |
|---|
| 58 |
static { |
|---|
| 59 |
bool delegate(pt)[DropTarget] targets; |
|---|
| 60 |
|
|---|
| 61 |
void opIndexAssign(bool delegate(pt) dg, DropTarget dt) { targets[dt] = dg; } |
|---|
| 62 |
bool checkDrop(Draggable dr, ref pt p) { |
|---|
| 63 |
foreach (target, dg; targets) { |
|---|
| 64 |
if (dg(p) && target.accept(dr, p)) return true; |
|---|
| 65 |
} |
|---|
| 66 |
return false; |
|---|
| 67 |
} |
|---|
| 68 |
} |
|---|
| 69 |
} |
|---|
| 70 |
|
|---|
| 71 |
struct ClickDragRelease { |
|---|
| 72 |
static { |
|---|
| 73 |
bool startDrag() { return mouse.clicked(Button.Left); } |
|---|
| 74 |
bool stopDrag() { return mouse.released(Button.Left); } |
|---|
| 75 |
} |
|---|
| 76 |
} |
|---|
| 77 |
|
|---|
| 78 |
struct ClickMoveClick { |
|---|
| 79 |
static { |
|---|
| 80 |
bool startDrag() { return mouse.clicked(Button.Left); } |
|---|
| 81 |
bool stopDrag() { return mouse.clicked(Button.Left); } |
|---|
| 82 |
} |
|---|
| 83 |
} |
|---|
| 84 |
|
|---|
| 85 |
struct HybridDrag { |
|---|
| 86 |
static { |
|---|
| 87 |
pt startPt; bool dragging; |
|---|
| 88 |
bool startDrag() { |
|---|
| 89 |
if (mouse.clicked(Button.Left)) { |
|---|
| 90 |
startPt = mouse.pos; |
|---|
| 91 |
return true; |
|---|
| 92 |
} |
|---|
| 93 |
return false; |
|---|
| 94 |
} |
|---|
| 95 |
bool stopDrag() { |
|---|
| 96 |
auto |
|---|
| 97 |
distpt = mouse.pos - startPt, |
|---|
| 98 |
distval = abs(distpt.x) + abs(distpt.y); |
|---|
| 99 |
if (distval > 16 && mouse.released(Button.Left)) return true; |
|---|
| 100 |
return mouse.clicked(Button.Left); |
|---|
| 101 |
} |
|---|
| 102 |
} |
|---|
| 103 |
} |
|---|
| 104 |
|
|---|
| 105 |
// alias DragDropFuns!(ClickDragRelease) DragDrop; |
|---|
| 106 |
// alias DragDropFuns!(ClickMoveClick) DragDrop; |
|---|
| 107 |
alias DragDropFuns!(HybridDrag) DragDrop; |
|---|
| 108 |
|
|---|
| 109 |
template castCall(T, string MEMBER) { |
|---|
| 110 |
void castCall(S)(S[] input) { |
|---|
| 111 |
foreach (value; input) |
|---|
| 112 |
if (auto asTee = cast(T) value) |
|---|
| 113 |
mixin("asTee."~MEMBER~"; "); |
|---|
| 114 |
} |
|---|
| 115 |
} |
|---|
| 116 |
|
|---|
| 117 |
class Grid : Drawable, DropTarget { |
|---|
| 118 |
Area area; |
|---|
| 119 |
int w, h; |
|---|
| 120 |
Draggable[] field; |
|---|
| 121 |
int draggingOver = -1; |
|---|
| 122 |
this(int w, int h) { |
|---|
| 123 |
this.w = w; this.h = h; |
|---|
| 124 |
// snap from outside |
|---|
| 125 |
area = display.select(32-16, 32-16, 32*w - 1, 32*h - 1); |
|---|
| 126 |
DropTargets[this] = area /apply/ (Area a, pt p) { |
|---|
| 127 |
return !!(p in a); |
|---|
| 128 |
}; |
|---|
| 129 |
field = new Draggable[w * h]; |
|---|
| 130 |
} |
|---|
| 131 |
override { // Drawable |
|---|
| 132 |
void draw() { |
|---|
| 133 |
for (int x = 0; x < w; x ++) { |
|---|
| 134 |
for (int y = 0; y < h; y ++) { |
|---|
| 135 |
auto sx = x * 32 + 32, sy = y * 32 + 32; |
|---|
| 136 |
auto id = y * w + x; |
|---|
| 137 |
if (id == draggingOver) line(sx, sy, sx + 32, sy + 32, |
|---|
| 138 |
Box=Black, Fill=Black~White~White); |
|---|
| 139 |
else line(sx, sy, sx + 32, sy + 32, Box=Black); |
|---|
| 140 |
} |
|---|
| 141 |
} |
|---|
| 142 |
castCall!(Drawable, "draw()")(field); |
|---|
| 143 |
} |
|---|
| 144 |
} |
|---|
| 145 |
pt snap(pt p) { |
|---|
| 146 |
pt pp; |
|---|
| 147 |
pp.x = (p.x + 16) & ~31; |
|---|
| 148 |
pp.y = (p.y + 16) & ~31; |
|---|
| 149 |
return pp; |
|---|
| 150 |
} |
|---|
| 151 |
int toID(pt p) { |
|---|
| 152 |
p.x = (p.x - area.tl.x) / 32; |
|---|
| 153 |
p.y = (p.y - area.tl.y) / 32; |
|---|
| 154 |
return p.y * w + p.x; |
|---|
| 155 |
} |
|---|
| 156 |
bool accept(Draggable dropping, ref pt p) { |
|---|
| 157 |
auto pp = snap(p), id = toID(pp); |
|---|
| 158 |
// we already know we're in the area |
|---|
| 159 |
if (field[id]) return false; |
|---|
| 160 |
p = pp; |
|---|
| 161 |
field[id] = dropping; |
|---|
| 162 |
dropping.setDragDg((ref proc dg) { |
|---|
| 163 |
dg = stuple(this, id) /apply/ (Grid That, int id) { |
|---|
| 164 |
That.field[id] = null; |
|---|
| 165 |
}; |
|---|
| 166 |
}); |
|---|
| 167 |
return true; |
|---|
| 168 |
} |
|---|
| 169 |
} |
|---|
| 170 |
|
|---|
| 171 |
template DefaultDrag(alias SIZE) { |
|---|
| 172 |
pt pos, dragStart, dragCursorStart; |
|---|
| 173 |
proc onDrag; |
|---|
| 174 |
void setDragArea() { |
|---|
| 175 |
DragDrop.drags[this] = display.select(pos.x, pos.y, SIZE.x, SIZE.y) |
|---|
| 176 |
/apply/ (Area a, pt p) { return !!(p in a); }; |
|---|
| 177 |
} |
|---|
| 178 |
override { |
|---|
| 179 |
void setDragDg(void delegate(ref proc) dg) { dg(onDrag); } |
|---|
| 180 |
void pickUp(pt p) { |
|---|
| 181 |
dragStart = pos; |
|---|
| 182 |
dragCursorStart = p; |
|---|
| 183 |
onDrag(); |
|---|
| 184 |
} |
|---|
| 185 |
void drag(pt p) { |
|---|
| 186 |
pos = dragStart + (p - dragCursorStart); |
|---|
| 187 |
} |
|---|
| 188 |
void drop(pt p) { |
|---|
| 189 |
pos = dragStart + (p - dragCursorStart); |
|---|
| 190 |
if (!DropTargets.checkDrop(this, pos)) { |
|---|
| 191 |
// Time to die, Mr. Anderson. |
|---|
| 192 |
// delete this; |
|---|
| 193 |
return; |
|---|
| 194 |
} |
|---|
| 195 |
setDragArea; |
|---|
| 196 |
} |
|---|
| 197 |
} |
|---|
| 198 |
} |
|---|
| 199 |
|
|---|
| 200 |
class Icon : Image, Draggable, Drawable { |
|---|
| 201 |
mixin DefaultDrag!(dimensions); |
|---|
| 202 |
this(void[] data, pt pos) { |
|---|
| 203 |
super(data); |
|---|
| 204 |
this.pos = pos; |
|---|
| 205 |
setDragArea; |
|---|
| 206 |
} |
|---|
| 207 |
override void draw() { display.select(pos.x, pos.y).blit(this); } |
|---|
| 208 |
} |
|---|
| 209 |
|
|---|
| 210 |
class GroupIcon : Draggable, Drawable { |
|---|
| 211 |
Area area; |
|---|
| 212 |
pt pos, dragStart, dragCursorStart; |
|---|
| 213 |
proc onDrag; |
|---|
| 214 |
void setDragArea() { |
|---|
| 215 |
DragDrop.drags[this] = display.select(pos.x, pos.y, 32, 32) |
|---|
| 216 |
/apply/ (Area a, pt p) { return !!(p in a); }; |
|---|
| 217 |
} |
|---|
| 218 |
bool follower, dropFailed; |
|---|
| 219 |
override { |
|---|
| 220 |
void setDragDg(void delegate(ref proc) dg) { dg(onDrag); } |
|---|
| 221 |
void pickUp(pt p) { |
|---|
| 222 |
dragStart = pos; |
|---|
| 223 |
dragCursorStart = p; |
|---|
| 224 |
onDrag(); |
|---|
| 225 |
if (follower) return; |
|---|
| 226 |
foreach (bro; brothers) { |
|---|
| 227 |
bro.follower = true; |
|---|
| 228 |
bro.pickUp(p + (bro.pos - pos)); |
|---|
| 229 |
DragDrop.drags.remove(bro); |
|---|
| 230 |
} |
|---|
| 231 |
} |
|---|
| 232 |
void drag(pt p) { |
|---|
| 233 |
auto oldpos = pos; |
|---|
| 234 |
pos = dragStart + (p - dragCursorStart); |
|---|
| 235 |
if (follower) return; |
|---|
| 236 |
foreach (bro; brothers) |
|---|
| 237 |
bro.drag(p + (bro.pos - oldpos)); |
|---|
| 238 |
} |
|---|
| 239 |
void drop(pt p) { |
|---|
| 240 |
auto oldpos = pos; |
|---|
| 241 |
pos = dragStart + (p - dragCursorStart); |
|---|
| 242 |
if (!DropTargets.checkDrop(this, pos)) { |
|---|
| 243 |
dropFailed = true; |
|---|
| 244 |
} |
|---|
| 245 |
if (follower) return; |
|---|
| 246 |
bool oneFailed = dropFailed; |
|---|
| 247 |
foreach (bro; brothers) { |
|---|
| 248 |
bro.drop(p + (bro.pos - oldpos)); |
|---|
| 249 |
if (bro.dropFailed) oneFailed = true; |
|---|
| 250 |
} |
|---|
| 251 |
if (oneFailed) { // IF ONE OF US FAILS, ALL OF US FAIL |
|---|
| 252 |
foreach (bro; brothers) { |
|---|
| 253 |
if (!bro.dropFailed) { |
|---|
| 254 |
bro.onDrag(); // drag body into the grave |
|---|
| 255 |
delete bro; |
|---|
| 256 |
} |
|---|
| 257 |
} |
|---|
| 258 |
if (!dropFailed) onDrag(); // drag ourselves loose. |
|---|
| 259 |
delete this; |
|---|
| 260 |
} else { |
|---|
| 261 |
foreach (bro; brothers) { |
|---|
| 262 |
bro.setDragArea; |
|---|
| 263 |
bro.follower = false; |
|---|
| 264 |
} |
|---|
| 265 |
setDragArea; |
|---|
| 266 |
} |
|---|
| 267 |
} |
|---|
| 268 |
void draw() { |
|---|
| 269 |
display.select(pos.x, pos.y).blit(area); |
|---|
| 270 |
if (follower) return; |
|---|
| 271 |
foreach (bro; brothers) |
|---|
| 272 |
if (bro.follower) |
|---|
| 273 |
bro.draw(); |
|---|
| 274 |
} |
|---|
| 275 |
} |
|---|
| 276 |
GroupIcon[] brothers; |
|---|
| 277 |
void linkUp(GroupIcon[] family) { |
|---|
| 278 |
foreach (gi; family) |
|---|
| 279 |
if (gi !is this) |
|---|
| 280 |
brothers ~= gi; |
|---|
| 281 |
} |
|---|
| 282 |
this(Area area, pt pos) { |
|---|
| 283 |
this.area = area; |
|---|
| 284 |
this.pos = pos; |
|---|
| 285 |
setDragArea; |
|---|
| 286 |
} |
|---|
| 287 |
} |
|---|
| 288 |
|
|---|
| 289 |
GroupIcon[] mkGroup(Area area, pt p) { |
|---|
| 290 |
auto w = area.width / 32, h = area.height / 32; |
|---|
| 291 |
GroupIcon[] res; |
|---|
| 292 |
for (int x = 0; x < w; ++x) |
|---|
| 293 |
for (int y = 0; y < h; ++y) |
|---|
| 294 |
res ~= new GroupIcon(area.select(x * 32, y * 32, 32, 32), p + pt(x * 32, y * 32)); |
|---|
| 295 |
foreach (gi; res) |
|---|
| 296 |
gi.linkUp(res); |
|---|
| 297 |
return res; |
|---|
| 298 |
} |
|---|
| 299 |
|
|---|
| 300 |
void main() { |
|---|
| 301 |
screen(640, 480); |
|---|
| 302 |
GroupIcon delegate(pt) genPizza; GroupIcon[] delegate() genPizzaz; |
|---|
| 303 |
GroupIcon[] pizzaz; |
|---|
| 304 |
void servePizza() { |
|---|
| 305 |
pizzaz = genPizzaz(); |
|---|
| 306 |
} |
|---|
| 307 |
void setServe(ref proc dg) { dg = &servePizza; }; |
|---|
| 308 |
auto pipes = new Image(import("straightpipe.png")); |
|---|
| 309 |
genPizzaz = { |
|---|
| 310 |
auto res = mkGroup(pipes, pt(312, 32)); |
|---|
| 311 |
foreach (icon; res) |
|---|
| 312 |
icon.setDragDg(&setServe); |
|---|
| 313 |
return res; |
|---|
| 314 |
}; |
|---|
| 315 |
servePizza; |
|---|
| 316 |
auto grid = new Grid(7, 7); |
|---|
| 317 |
while (true) { |
|---|
| 318 |
cls(White); |
|---|
| 319 |
grid.draw; |
|---|
| 320 |
foreach (pizza; pizzaz) |
|---|
| 321 |
pizza.draw; |
|---|
| 322 |
DragDrop.draw; |
|---|
| 323 |
flip; |
|---|
| 324 |
events; |
|---|
| 325 |
DragDrop.checkEvents; |
|---|
| 326 |
if (DragDrop.selected && key.pressed(SDLKey.LCtrl)) { |
|---|
| 327 |
logln("Rotate pic!"); |
|---|
| 328 |
DragDrop.selected = null; |
|---|
| 329 |
auto npipes = pipes.rot_right(); |
|---|
| 330 |
|
|---|
| 331 |
} |
|---|
| 332 |
} |
|---|
| 333 |
} |
|---|