1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 5 6 module dfl.imagelist; 7 8 import dfl.base, dfl.drawing, dfl.internal.winapi; 9 import dfl.collections; 10 11 12 version(DFL_NO_IMAGELIST) { 13 } 14 else { 15 16 class ImageList { // docmain 17 18 class ImageCollection { 19 protected this() { 20 } 21 22 23 void insert(int index, Image img) { 24 if(index >= _images.length) { 25 add(img); 26 } else { 27 assert(0, "Must add images to the end of the image list"); 28 } 29 } 30 31 32 final void addStrip(Image img) { 33 HGDIOBJ hgo; 34 if(1 != img._imgtype(&hgo)) { 35 debug { 36 assert(0, "Image list: addStrip needs bitmap"); 37 } 38 else { 39 _unableimg(); 40 } 41 } 42 43 auto sz = imageSize; 44 if(img.height != sz.height 45 || img.width % sz.width) { 46 debug { 47 assert(0, "Image list: invalid image size"); 48 } 49 else { 50 _unableimg(); 51 } 52 } 53 int num = img.width / sz.width; 54 55 /+ 56 if(1 == num) { 57 add(img); 58 return; 59 } 60 +/ 61 62 auto _hdl = handle; // _addhbitmap needs the handle! Could avoid this in the future. 63 _addhbitmap(hgo); 64 65 int x = 0; 66 for(; num; num--) { 67 auto sp = new StripPart(); 68 sp.origImg = img; 69 sp.hbm = hgo; 70 sp.partBounds = Rect(x, 0, sz.width, sz.height); 71 72 _images ~= sp; 73 74 x += sz.width; 75 } 76 } 77 78 79 package: 80 81 Image[] _images; 82 83 84 static class StripPart: Image { 85 override @property Size size() { // getter 86 return partBounds.size; 87 } 88 89 90 override void draw(Graphics g, Point pt) { 91 HDC memdc; 92 memdc = CreateCompatibleDC(g.handle); 93 try { 94 HGDIOBJ hgo; 95 hgo = SelectObject(memdc, hbm); 96 BitBlt(g.handle, pt.x, pt.y, partBounds.width, partBounds.height, memdc, partBounds.x, partBounds.y, SRCCOPY); 97 SelectObject(memdc, hgo); // Old bitmap. 98 } 99 finally { 100 DeleteDC(memdc); 101 } 102 } 103 104 105 override void drawStretched(Graphics g, Rect r) { 106 HDC memdc; 107 memdc = CreateCompatibleDC(g.handle); 108 try { 109 HGDIOBJ hgo; 110 int lstretch; 111 hgo = SelectObject(memdc, hbm); 112 lstretch = SetStretchBltMode(g.handle, COLORONCOLOR); 113 StretchBlt(g.handle, r.x, r.y, r.width, r.height, 114 memdc, partBounds.x, partBounds.y, partBounds.width, partBounds.height, SRCCOPY); 115 SetStretchBltMode(g.handle, lstretch); 116 SelectObject(memdc, hgo); // Old bitmap. 117 } 118 finally { 119 DeleteDC(memdc); 120 } 121 } 122 123 124 Image origImg; // Hold this so the HBITMAP doesn't get collected. 125 HBITMAP hbm; 126 Rect partBounds; 127 } 128 129 130 void _adding(size_t idx, Image val) { 131 assert(val !is null); 132 133 switch(val._imgtype(null)) { 134 case 1: 135 case 2: 136 break; 137 default: 138 debug { 139 assert(0, "Image list: invalid image type"); 140 } 141 else { 142 _unableimg(); 143 } 144 } 145 146 if(val.size != imageSize) { 147 debug { 148 assert(0, "Image list: invalid image size"); 149 } 150 else { 151 _unableimg(); 152 } 153 } 154 } 155 156 157 void _added(size_t idx, Image val) { 158 if(isHandleCreated) { 159 //if(idx >= _images.length) // Can't test for this here because -val- is already added to the array. 160 _addimg(val); 161 } 162 } 163 164 165 void _removed(size_t idx, Image val) { 166 if(isHandleCreated) { 167 if(size_t.max == idx) { // Clear all. 168 imageListRemove(handle, -1); 169 } else { 170 imageListRemove(handle, idx); 171 } 172 } 173 } 174 175 176 public: 177 178 mixin ListWrapArray!(Image, _images, 179 _adding, _added, 180 _blankListCallback!(Image), _removed, 181 false, false, false); 182 } 183 184 185 this() { 186 InitCommonControls(); 187 188 _cimages = new ImageCollection(); 189 _transcolor = Color.transparent; 190 } 191 192 193 194 final @property void colorDepth(ColorDepth depth) { // setter 195 assert(!isHandleCreated); 196 197 this._depth = depth; 198 } 199 200 /// ditto 201 final @property ColorDepth colorDepth() { // getter 202 return _depth; 203 } 204 205 206 207 final @property void transparentColor(Color tc) { // setter 208 assert(!isHandleCreated); 209 210 _transcolor = tc; 211 } 212 213 /// ditto 214 final @property Color transparentColor() { // getter 215 return _transcolor; 216 } 217 218 219 220 final @property void imageSize(Size sz) { // setter 221 assert(!isHandleCreated); 222 223 assert(sz.width && sz.height); 224 225 _w = sz.width; 226 _h = sz.height; 227 } 228 229 /// ditto 230 final @property Size imageSize() { // getter 231 return Size(_w, _h); 232 } 233 234 235 236 final @property ImageCollection images() { // getter 237 return _cimages; 238 } 239 240 241 242 final @property void tag(Object t) { // setter 243 this._tag = t; 244 } 245 246 /// ditto 247 final @property Object tag() { // getter 248 return this._tag; 249 } 250 251 252 /+ // Actually, forget about these; just draw with the actual images. 253 254 final void draw(Graphics g, Point pt, int index) { 255 return draw(g, pt.x, pt.y, index); 256 } 257 258 /// ditto 259 final void draw(Graphics g, int x, int y, int index) { 260 imageListDraw(handle, index, g.handle, x, y, ILD_NORMAL); 261 } 262 263 /// ditto 264 // stretch 265 final void draw(Graphics g, int x, int y, int width, int height, int index) { 266 // ImageList_DrawEx operates differently if the width or height is zero 267 // so bail out if zero and pretend the zero size image was drawn. 268 if(!width) { 269 return; 270 } 271 if(!height) { 272 return; 273 } 274 275 imageListDrawEx(handle, index, g.handle, x, y, width, height, 276 CLR_NONE, CLR_NONE, ILD_NORMAL); // ? 277 } 278 +/ 279 280 281 282 final @property bool isHandleCreated() { // getter 283 return HIMAGELIST.init != _hil; 284 } 285 286 deprecated alias isHandleCreated handleCreated; 287 288 289 290 final @property HIMAGELIST handle() { // getter 291 if(!isHandleCreated) { 292 _createimagelist(); 293 } 294 return _hil; 295 } 296 297 298 299 void dispose() { 300 return dispose(true); 301 } 302 303 /// ditto 304 void dispose(bool disposing) { 305 if(isHandleCreated) { 306 imageListDestroy(_hil); 307 } 308 _hil = HIMAGELIST.init; 309 310 if(disposing) { 311 //_cimages._images = null; // Not GC-safe in dtor. 312 //_cimages = null; // Could cause bad things. 313 } 314 } 315 316 317 ~this() { 318 dispose(); 319 } 320 321 322 private: 323 324 ColorDepth _depth = ColorDepth.DEPTH_8BIT; 325 Color _transcolor; 326 ImageCollection _cimages; 327 HIMAGELIST _hil; 328 int _w = 16, _h = 16; 329 Object _tag; 330 331 332 void _createimagelist() { 333 if(isHandleCreated) { 334 imageListDestroy(_hil); 335 _hil = HIMAGELIST.init; 336 } 337 338 UINT flags = ILC_MASK; 339 switch(_depth) { 340 case ColorDepth.DEPTH_4BIT: flags |= ILC_COLOR4; break; 341 default: case ColorDepth.DEPTH_8BIT: flags |= ILC_COLOR8; break; 342 case ColorDepth.DEPTH_16BIT: flags |= ILC_COLOR16; break; 343 case ColorDepth.DEPTH_24BIT: flags |= ILC_COLOR24; break; 344 case ColorDepth.DEPTH_32BIT: flags |= ILC_COLOR32; break; 345 } 346 347 // Note: cGrow is not a limit, but how many images to preallocate each grow. 348 _hil = imageListCreate(_w, _h, flags, _cimages._images.length, 4 + _cimages._images.length / 4); 349 if(!_hil) { 350 throw new DflException("Unable to create image list"); 351 } 352 353 foreach(img; _cimages._images) { 354 _addimg(img); 355 } 356 } 357 358 359 void _unableimg() { 360 throw new DflException("Unable to add image to image list"); 361 } 362 363 364 int _addimg(Image img) { 365 assert(isHandleCreated); 366 367 HGDIOBJ hgo; 368 int result; 369 switch(img._imgtype(&hgo)) { 370 case 1: 371 result = _addhbitmap(hgo); 372 break; 373 374 case 2: 375 result = imageListAddIcon(_hil, cast(HICON)hgo); 376 break; 377 378 default: 379 result = -1; 380 } 381 382 //if(-1 == result) 383 // _unableimg(); 384 return result; 385 } 386 387 int _addhbitmap(HBITMAP hbm) { 388 assert(isHandleCreated); 389 390 COLORREF cr; 391 if(_transcolor == Color.empty 392 || _transcolor == Color.transparent) { 393 cr = CLR_NONE; // ? 394 } else { 395 cr = _transcolor.toRgb(); 396 } 397 return imageListAddMasked(_hil, cast(HBITMAP)hbm, cr); 398 } 399 } 400 401 402 private extern(Windows) { 403 // This was the only way I could figure out how to use the current actctx (Windows issue). 404 405 HIMAGELIST imageListCreate( 406 int cx, int cy, UINT flags, int cInitial, int cGrow) { 407 alias typeof(&ImageList_Create) TProc; 408 static TProc proc = null; 409 if(!proc) { 410 proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_Create"); 411 } 412 return proc(cx, cy, flags, cInitial, cGrow); 413 } 414 415 int imageListAddIcon( 416 HIMAGELIST himl, HICON hicon) { 417 alias typeof(&ImageList_AddIcon) TProc; 418 static TProc proc = null; 419 if(!proc) { 420 proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_AddIcon"); 421 } 422 return proc(himl, hicon); 423 } 424 425 int imageListAddMasked( 426 HIMAGELIST himl, HBITMAP hbmImage, COLORREF crMask) { 427 alias typeof(&ImageList_AddMasked) TProc; 428 static TProc proc = null; 429 if(!proc) { 430 proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_AddMasked"); 431 } 432 return proc(himl, hbmImage, crMask); 433 } 434 435 BOOL imageListRemove( 436 HIMAGELIST himl, int i) { 437 alias typeof(&ImageList_Remove) TProc; 438 static TProc proc = null; 439 if(!proc) { 440 proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_Remove"); 441 } 442 return proc(himl, i); 443 } 444 445 BOOL imageListDestroy( 446 HIMAGELIST himl) { 447 alias typeof(&ImageList_Destroy) TProc; 448 static TProc proc = null; 449 if(!proc) { 450 proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_Destroy"); 451 } 452 return proc(himl); 453 } 454 } 455 } 456