From e76394c5da9b7a43a9468e72d2191ee2c43a67ce Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 18 Mar 2014 15:19:20 +0400 Subject: [PATCH] rework resources destroy --- examples/example1/main.d | 12 +- src/dlangui/graphics/drawbuf.d | 7 + src/dlangui/graphics/fonts.d | 16 +- src/dlangui/graphics/ftfonts.d | 29 +- src/dlangui/graphics/images.d | 798 ++++++++++++------------ src/dlangui/platforms/common/platform.d | 6 + src/dlangui/platforms/x11/x11app.d | 18 +- src/dlangui/widgets/controls.d | 27 +- src/dlangui/widgets/styles.d | 49 +- src/dlangui/widgets/widget.d | 46 +- 10 files changed, 574 insertions(+), 434 deletions(-) diff --git a/examples/example1/main.d b/examples/example1/main.d index 89bf572b..ca25b42e 100644 --- a/examples/example1/main.d +++ b/examples/example1/main.d @@ -43,7 +43,10 @@ extern (C) int UIAppMain(string[] args) { LinearLayout layout = new LinearLayout(); layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0")); layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); - layout.addChild((new Button()).text("Button1")); //.textColor(0x40FF4000) + layout.addChild((new Button("BTN1")).text("Button1")); //.textColor(0x40FF4000) + + + LinearLayout hlayout = new HorizontalLayout(); //hlayout.addChild((new Button()).text("<<")); //.textColor(0x40FF4000) @@ -62,13 +65,16 @@ extern (C) int UIAppMain(string[] args) { vlayout.addChild((new TextWidget()).text("VLayout line 2").textColor(0x40FFFF00)); layout.addChild(vlayout); - layout.addChild((new Button()).textColor(0x000000FF).text("Button2")); + layout.addChild((new Button("BTN2")).textColor(0x000000FF).text("Button2")); layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); layout.addChild((new ImageWidget()).drawableId("exit").padding(Rect(5,5,5,5))); layout.addChild((new TextWidget()).textColor(0xFF4000).text("Text widget2").padding(Rect(5,5,5,5)).margins(Rect(5,5,5,5)).backgroundColor(0xA0A0A0)); - layout.addChild((new Button()).textColor(0x000000FF).text("Button3").layoutHeight(FILL_PARENT)); + layout.addChild((new Button("BTN3")).textColor(0x000000FF).text("Button3").layoutHeight(FILL_PARENT)); layout.addChild((new TextWidget()).textColor(0x004000).text("Text widget3 with very long text")); + layout.childById("BTN1").onClickListener(delegate (Widget w) { Log.d("onClick ", w.id); return true; }); + layout.childById("BTN2").onClickListener(delegate (Widget w) { Log.d("onClick ", w.id); return true; }); + layout.childById("BTN3").onClickListener(delegate (Widget w) { Log.d("onClick ", w.id); return true; }); layout.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT); diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index bab39d3e..9ac4fa0b 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -670,6 +670,13 @@ class ColorDrawBuf : ColorDrawBufBase { } class Drawable : RefCountedObject { + private static int _instanceCount; + this() { + Log.d("Created drawable, count=", ++_instanceCount); + } + ~this() { + Log.d("Destroyed drawable, count=", --_instanceCount); + } abstract void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0); @property abstract int width(); @property abstract int height(); diff --git a/src/dlangui/graphics/fonts.d b/src/dlangui/graphics/fonts.d index 95545ef6..5e0f700a 100644 --- a/src/dlangui/graphics/fonts.d +++ b/src/dlangui/graphics/fonts.d @@ -146,9 +146,19 @@ struct FontList { FontRef[] _list; uint _len; ~this() { + clear(); + } + + @property uint length() { + return _len; + } + + void clear() { for (uint i = 0; i < _len; i++) { _list[i].clear(); + _list[i] = null; } + _len = 0; } // returns item by index ref FontRef get(int index) { @@ -216,6 +226,8 @@ class FontManager { static __gshared FontManager _instance; /// sets new font manager singleton instance static @property void instance(FontManager manager) { + if (_instance !is null) + destroy(_instance); _instance = manager; } /// returns font manager singleton instance @@ -232,5 +244,7 @@ class FontManager { /// removes entries not used after last call of checkpoint() or cleanup() abstract void cleanup(); - ~this() {} + ~this() { + Log.d("Destroying font manager"); + } } diff --git a/src/dlangui/graphics/ftfonts.d b/src/dlangui/graphics/ftfonts.d index 8307e22e..7170bf4b 100644 --- a/src/dlangui/graphics/ftfonts.d +++ b/src/dlangui/graphics/ftfonts.d @@ -99,6 +99,7 @@ private class FreeTypeFontFile { @property int weight() { return _weight; } @property bool italic() { return _italic; } + private static int _instanceCount; this(FT_Library library, string filename) { _library = library; _filename = filename; @@ -106,6 +107,12 @@ private class FreeTypeFontFile { _matrix.yy = 0x10000; _matrix.xy = 0; _matrix.yx = 0; + Log.d("Created FreeTypeFontFile, count=", ++_instanceCount); + } + + ~this() { + clear(); + Log.d("Destroyed FreeTypeFontFile, count=", --_instanceCount); } private static string familyName(FT_Face face) @@ -273,9 +280,6 @@ private class FreeTypeFontFile { _face = null; } - ~this() { - clear(); - } } /** @@ -285,22 +289,26 @@ class FreeTypeFont : Font { private FontFileItem _fontItem; private FreeTypeFontFile[] _files; + static int _instanceCount; /// need to call create() after construction to initialize font this(FontFileItem item, int size) { _fontItem = item; _size = size; _height = size; + Log.d("Created font, count=", ++_instanceCount); } + /// do cleanup + ~this() { + clear(); + Log.d("Destroyed font, count=", --_instanceCount); + } + private int _size; private int _height; private GlyphCache _glyphCache; - /// do cleanup - ~this() { - clear(); - } /// cleanup resources override void clear() { @@ -482,6 +490,13 @@ class FreeTypeFontManager : FontManager { } } ~this() { + Log.d("FreeTypeFontManager ~this() active fonts: ", _activeFonts.length); + _activeFonts.clear(); + foreach(ref FontFileItem item; _fontFiles) { + destroy(item); + item = null; + } + _fontFiles.length = 0; // uninit library if (_library) FT_Done_FreeType(_library); diff --git a/src/dlangui/graphics/images.d b/src/dlangui/graphics/images.d index f725a531..7c7f628b 100644 --- a/src/dlangui/graphics/images.d +++ b/src/dlangui/graphics/images.d @@ -1,61 +1,61 @@ -module dlangui.graphics.images; - -import dlangui.core.logger; -import dlangui.core.types; -import dlangui.graphics.drawbuf; -import std.stream; -import std.file; -import std.algorithm; -import libpng.png; - -/// decoded image cache -class ImageCache { - - static class ImageCacheItem { - string _filename; - DrawBufRef _drawbuf; - bool _error; // flag to avoid loading of file if it has been failed once - bool _used; - this(string filename) { - _filename = filename; - } - @property ref DrawBufRef get() { - if (!_drawbuf.isNull || _error) { - _used = true; - return _drawbuf; - } - _drawbuf = loadImage(_filename); - _used = true; - if (_drawbuf.isNull) - _error = true; - return _drawbuf; - } - /// remove from memory, will cause reload on next access - void compact() { - if (!_drawbuf.isNull) - _drawbuf.clear(); - } - /// mark as not used - void checkpoint() { - _used = false; - } - /// cleanup if unused since last checkpoint +module dlangui.graphics.images; + +import dlangui.core.logger; +import dlangui.core.types; +import dlangui.graphics.drawbuf; +import std.stream; +import std.file; +import std.algorithm; +import libpng.png; + +/// decoded image cache +class ImageCache { + + static class ImageCacheItem { + string _filename; + DrawBufRef _drawbuf; + bool _error; // flag to avoid loading of file if it has been failed once + bool _used; + this(string filename) { + _filename = filename; + } + @property ref DrawBufRef get() { + if (!_drawbuf.isNull || _error) { + _used = true; + return _drawbuf; + } + _drawbuf = loadImage(_filename); + _used = true; + if (_drawbuf.isNull) + _error = true; + return _drawbuf; + } + /// remove from memory, will cause reload on next access + void compact() { + if (!_drawbuf.isNull) + _drawbuf.clear(); + } + /// mark as not used + void checkpoint() { + _used = false; + } + /// cleanup if unused since last checkpoint void cleanup() { if (!_used) compact(); } - } - ImageCacheItem[string] _map; - - /// get and cache image - ref DrawBufRef get(string filename) { - if (filename in _map) { - return _map[filename].get; - } - ImageCacheItem item = new ImageCacheItem(filename); - _map[filename] = item; - return item.get; - } + } + ImageCacheItem[string] _map; + + /// get and cache image + ref DrawBufRef get(string filename) { + if (filename in _map) { + return _map[filename].get; + } + ImageCacheItem item = new ImageCacheItem(filename); + _map[filename] = item; + return item.get; + } // clear usage flags for all entries void checkpoint() { foreach (item; _map) @@ -73,71 +73,87 @@ class ImageCache { ~this() { Log.i("Destroying ImageCache"); } -} - -__gshared ImageCache _imageCache; -/// image cache singleton -@property ImageCache imageCache() { return _imageCache; } - -__gshared DrawableCache _drawableCache; -/// drawable cache singleton -@property DrawableCache drawableCache() { return _drawableCache; } - -shared static this() { - _imageCache = new ImageCache(); - _drawableCache = new DrawableCache(); -} - -class DrawableCache { - static class DrawableCacheItem { - string _id; - string _filename; - bool _tiled; - bool _error; - bool _used; - DrawableRef _drawable; - this(string id, string filename, bool tiled) { - _id = id; - _filename = filename; - _tiled = tiled; - _error = filename is null; - } - /// remove from memory, will cause reload on next access - void compact() { - if (!_drawable.isNull) - _drawable.clear(); - } - /// mark as not used - void checkpoint() { - _used = false; - } - /// cleanup if unused since last checkpoint +} + +__gshared ImageCache _imageCache; +/// image cache singleton +@property ImageCache imageCache() { return _imageCache; } +/// image cache singleton +@property void imageCache(ImageCache cache) { + if (_imageCache !is null) + destroy(_imageCache); + _imageCache = cache; +} + +__gshared DrawableCache _drawableCache; +/// drawable cache singleton +@property DrawableCache drawableCache() { return _drawableCache; } +/// drawable cache singleton +@property void drawableCache(DrawableCache cache) { + if (_drawableCache !is null) + destroy(_drawableCache); + _drawableCache = cache; +} + +shared static this() { + _imageCache = new ImageCache(); + _drawableCache = new DrawableCache(); +} + +class DrawableCache { + static class DrawableCacheItem { + string _id; + string _filename; + bool _tiled; + bool _error; + bool _used; + DrawableRef _drawable; + this(string id, string filename, bool tiled) { + _id = id; + _filename = filename; + _tiled = tiled; + _error = filename is null; + } + ~this() { + _drawable.clear(); + } + /// remove from memory, will cause reload on next access + void compact() { + if (!_drawable.isNull) + _drawable.clear(); + } + /// mark as not used + void checkpoint() { + _used = false; + } + /// cleanup if unused since last checkpoint void cleanup() { if (!_used) compact(); } - @property ref DrawableRef drawable() { - _used = true; - if (!_drawable.isNull || _error) - return _drawable; - if (_filename !is null) { - // reload from file - DrawBufRef image = imageCache.get(_filename); - if (!image.isNull) { - bool ninePatch = _filename.endsWith(".9.png"); - _drawable = new ImageDrawable(image, _tiled, ninePatch); - } else - _error = true; - } - return _drawable; - } - } - void clear() { - _idToFileMap.clear(); - foreach(DrawableCacheItem item; _idToDrawableMap) - item.drawable.clear(); - _idToDrawableMap.clear(); - } + @property ref DrawableRef drawable() { + _used = true; + if (!_drawable.isNull || _error) + return _drawable; + if (_filename !is null) { + // reload from file + DrawBufRef image = imageCache.get(_filename); + if (!image.isNull) { + bool ninePatch = _filename.endsWith(".9.png"); + _drawable = new ImageDrawable(image, _tiled, ninePatch); + } else + _error = true; + } + return _drawable; + } + } + void clear() { + Log.d("DrawableCache.clear()"); + _idToFileMap.clear(); + foreach(DrawableCacheItem item; _idToDrawableMap) + item.drawable.clear(); + _idToDrawableMap.clear(); + } // clear usage flags for all entries void checkpoint() { foreach (item; _idToDrawableMap) @@ -148,286 +164,286 @@ class DrawableCache { foreach (item; _idToDrawableMap) item.cleanup(); } - string[] _resourcePaths; - string[string] _idToFileMap; - DrawableCacheItem[string] _idToDrawableMap; - ref DrawableRef get(string id) { - if (id in _idToDrawableMap) - return _idToDrawableMap[id].drawable; - string resourceId = id; - bool tiled = false; - if (id.endsWith(".tiled")) { - resourceId = id[0..$-6]; // remove .tiled - tiled = true; - } - string filename = findResource(resourceId); - DrawableCacheItem item = new DrawableCacheItem(id, filename, tiled); - _idToDrawableMap[id] = item; - return item.drawable; - } - @property string[] resourcePaths() { - return _resourcePaths; - } - @property void resourcePaths(string[] paths) { - _resourcePaths = paths; - clear(); - } - string findResource(string id) { - if (id in _idToFileMap) - return _idToFileMap[id]; - foreach(string path; _resourcePaths) { - char[] name = path.dup; - name ~= id; - name ~= ".png"; - if (!exists(name)) { - name = path.dup; - name ~= id; - name ~= ".9.png"; - } - if (exists(name) && isFile(name)) { - string filename = name.dup; - _idToFileMap[id] = filename; - return filename; - } - } - return null; - } + string[] _resourcePaths; + string[string] _idToFileMap; + DrawableCacheItem[string] _idToDrawableMap; + ref DrawableRef get(string id) { + if (id in _idToDrawableMap) + return _idToDrawableMap[id].drawable; + string resourceId = id; + bool tiled = false; + if (id.endsWith(".tiled")) { + resourceId = id[0..$-6]; // remove .tiled + tiled = true; + } + string filename = findResource(resourceId); + DrawableCacheItem item = new DrawableCacheItem(id, filename, tiled); + _idToDrawableMap[id] = item; + return item.drawable; + } + @property string[] resourcePaths() { + return _resourcePaths; + } + @property void resourcePaths(string[] paths) { + _resourcePaths = paths; + clear(); + } + string findResource(string id) { + if (id in _idToFileMap) + return _idToFileMap[id]; + foreach(string path; _resourcePaths) { + char[] name = path.dup; + name ~= id; + name ~= ".png"; + if (!exists(name)) { + name = path.dup; + name ~= id; + name ~= ".9.png"; + } + if (exists(name) && isFile(name)) { + string filename = name.dup; + _idToFileMap[id] = filename; + return filename; + } + } + return null; + } this() { Log.i("Creating DrawableCache"); } ~this() { Log.i("Destroying DrawableCache"); } -} - -/// load and decode image from file to ColorDrawBuf, returns null if loading or decoding is failed -ColorDrawBuf loadImage(string filename) { - Log.d("Loading image from file " ~ filename); - try { - std.stream.File f = new std.stream.File(filename); - scope(exit) { f.close(); } - return loadImage(f); - } catch (Exception e) { - return null; - } -} - -/// load and decode image from stream to ColorDrawBuf, returns null if loading or decoding is failed -ColorDrawBuf loadImage(InputStream stream) { - if (stream is null || !stream.isOpen) - return null; - static if (USE_FREEIMAGE) { - return loadFreeImage(stream); - } else static if (USE_LIBPNG) { - return loadPngImage(stream); - } -} - -class ImageDecodingException : Exception { - this(string msg) { - super(msg); - } -} - -immutable bool USE_LIBPNG = false; -immutable bool USE_FREEIMAGE = true; - -shared static this() { - //import derelict.freeimage.freeimage; - //DerelictFI.load(); -} - -static if (USE_FREEIMAGE) { - ColorDrawBuf loadFreeImage(InputStream stream) { - import derelict.freeimage.freeimage; - - static bool FREE_IMAGE_LOADED; - if (!FREE_IMAGE_LOADED) { - DerelictFI.load(); - FREE_IMAGE_LOADED = true; - } - - ubyte imagebuf[]; - ubyte readbuf[4096]; - for (;;) { - size_t bytesRead = stream.read(readbuf); - if (!bytesRead) - break; - imagebuf ~= readbuf[0..bytesRead]; - } - //pointer to the image, once loaded - FIBITMAP *dib = null; //image format - FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; - // attach the binary data to a memory stream - FIMEMORY *hmem = FreeImage_OpenMemory(imagebuf.ptr, imagebuf.length); - fif = FreeImage_GetFileTypeFromMemory(hmem); - //check that the plugin has reading capabilities and load the file - if(!FreeImage_FIFSupportsReading(fif)) { - FreeImage_CloseMemory(hmem); - return null; - } - - // load an image from the memory stream - dib = FreeImage_LoadFromMemory(fif, hmem, 0); - - //if the image failed to load, return failure - if (!dib) { - Log.e("Failed to decode image"); - FreeImage_CloseMemory(hmem); - return null; - } - //retrieve the image data - ubyte * data = cast(ubyte*)FreeImage_GetBits(dib); - //get the image width and height, and size per pixel - int width = FreeImage_GetWidth(dib); - int height = FreeImage_GetHeight(dib); - int pixelSize = FreeImage_GetBPP(dib)/8; - int size = width*height*pixelSize; - - ColorDrawBuf res = new ColorDrawBuf(width, height); - - //swap R and B and invert image while copying - ubyte* src; - uint* dst; - uint r, g, b, a; - for( int i = 0, ii = height-1; i < height ; ++i, --ii ) { - dst = res.scanLine(i); - src = data + (ii * width) * pixelSize; - for( int j = 0; j < width; ++j, ++dst, src += pixelSize ) { - a = 0; - switch (pixelSize) { - case 4: - a = src[3] ^ 255; - case 3: - r = src[2]; - g = src[1]; - b = src[0]; - break; - case 2: - // todo: do something better - r = g = src[1]; - b = src[0]; - break; - default: - case 1: - r = g = b = src[0]; - break; - } - dst[0] = (a << 24) | (r << 16) | (g << 8) | b; - } - } - FreeImage_CloseMemory(hmem); - return res; - } -} - -static if (USE_LIBPNG) { - - extern (C) void lvpng_error_func (png_structp png, png_const_charp msg) - { - string s = fromStringz(msg); - Log.d("Error while reading PNG image: ", s); - // todo: exceptions do not work inside C function - throw new ImageDecodingException("Error while decoding PNG image"); - } - - extern (C) void lvpng_warning_func (png_structp png, png_const_charp msg) - { - string s = fromStringz(msg); - Log.d("Warn while reading PNG image: ", s); - // todo: exceptions do not work inside C function - throw new ImageDecodingException("Error while decoding PNG image"); - } - - extern (C) void lvpng_read_func(png_structp png, png_bytep buf, png_size_t len) - { - InputStream stream = cast(InputStream)png_get_io_ptr(png); - ubyte[] localbuf = new ubyte[len]; - if (stream.read(localbuf) != len) - throw new ImageDecodingException("Error while reading PNG image"); - for (uint i = 0; i < len; i++) - buf[i] = localbuf[i]; - } - - /// load and decode PNG image - ColorDrawBuf loadPngImage(InputStream stream) - { - png_structp png_ptr = null; - png_infop info_ptr = null; - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, - cast(png_voidp)stream, &lvpng_error_func, &lvpng_warning_func); - if ( !png_ptr ) - return null; - - try { - // - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) - lvpng_error_func(png_ptr, "cannot create png info struct"); - png_set_read_fn(png_ptr, - cast(void*)stream, &lvpng_read_func); - png_read_info( png_ptr, info_ptr ); - - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type; - png_get_IHDR(png_ptr, info_ptr, &width, &height, - &bit_depth, &color_type, &interlace_type, - null, null); - ColorDrawBuf drawbuf = new ColorDrawBuf(width, height); - - if (color_type & PNG_COLOR_MASK_PALETTE) - png_set_palette_to_rgb(png_ptr); - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) - png_set_expand_gray_1_2_4_to_8(png_ptr); - - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) - png_set_tRNS_to_alpha(png_ptr); - - if (bit_depth == 16) - png_set_strip_16(png_ptr); - - png_set_invert_alpha(png_ptr); - - if (bit_depth < 8) - png_set_packing(png_ptr); - - png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); - - if (color_type == PNG_COLOR_TYPE_GRAY || - color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png_ptr); - - int number_passes = png_set_interlace_handling(png_ptr); - png_set_bgr(png_ptr); - - for (int pass = 0; pass < number_passes; pass++) - { - for (int y = 0; y < height; y++) - { - uint * row = drawbuf.scanLine(y); - png_read_rows(png_ptr, cast(ubyte **)&row, null, 1); - } - } - - png_read_end(png_ptr, info_ptr); - - png_destroy_read_struct(&png_ptr, &info_ptr, null); - - return drawbuf; - } catch (ImageDecodingException e) { - if (png_ptr) - { - png_destroy_read_struct(&png_ptr, &info_ptr, null); - } - return null; - } - } - - //bool LVPngImageSource::CheckPattern( const lUInt8 * buf, int ) - //{ - //return( !png_sig_cmp((unsigned char *)buf, (png_size_t)0, 4) ); - //} - +} + +/// load and decode image from file to ColorDrawBuf, returns null if loading or decoding is failed +ColorDrawBuf loadImage(string filename) { + Log.d("Loading image from file " ~ filename); + try { + std.stream.File f = new std.stream.File(filename); + scope(exit) { f.close(); } + return loadImage(f); + } catch (Exception e) { + return null; + } +} + +/// load and decode image from stream to ColorDrawBuf, returns null if loading or decoding is failed +ColorDrawBuf loadImage(InputStream stream) { + if (stream is null || !stream.isOpen) + return null; + static if (USE_FREEIMAGE) { + return loadFreeImage(stream); + } else static if (USE_LIBPNG) { + return loadPngImage(stream); + } +} + +class ImageDecodingException : Exception { + this(string msg) { + super(msg); + } +} + +immutable bool USE_LIBPNG = false; +immutable bool USE_FREEIMAGE = true; + +shared static this() { + //import derelict.freeimage.freeimage; + //DerelictFI.load(); +} + +static if (USE_FREEIMAGE) { + ColorDrawBuf loadFreeImage(InputStream stream) { + import derelict.freeimage.freeimage; + + static bool FREE_IMAGE_LOADED; + if (!FREE_IMAGE_LOADED) { + DerelictFI.load(); + FREE_IMAGE_LOADED = true; + } + + ubyte imagebuf[]; + ubyte readbuf[4096]; + for (;;) { + size_t bytesRead = stream.read(readbuf); + if (!bytesRead) + break; + imagebuf ~= readbuf[0..bytesRead]; + } + //pointer to the image, once loaded + FIBITMAP *dib = null; //image format + FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; + // attach the binary data to a memory stream + FIMEMORY *hmem = FreeImage_OpenMemory(imagebuf.ptr, imagebuf.length); + fif = FreeImage_GetFileTypeFromMemory(hmem); + //check that the plugin has reading capabilities and load the file + if(!FreeImage_FIFSupportsReading(fif)) { + FreeImage_CloseMemory(hmem); + return null; + } + + // load an image from the memory stream + dib = FreeImage_LoadFromMemory(fif, hmem, 0); + + //if the image failed to load, return failure + if (!dib) { + Log.e("Failed to decode image"); + FreeImage_CloseMemory(hmem); + return null; + } + //retrieve the image data + ubyte * data = cast(ubyte*)FreeImage_GetBits(dib); + //get the image width and height, and size per pixel + int width = FreeImage_GetWidth(dib); + int height = FreeImage_GetHeight(dib); + int pixelSize = FreeImage_GetBPP(dib)/8; + int size = width*height*pixelSize; + + ColorDrawBuf res = new ColorDrawBuf(width, height); + + //swap R and B and invert image while copying + ubyte* src; + uint* dst; + uint r, g, b, a; + for( int i = 0, ii = height-1; i < height ; ++i, --ii ) { + dst = res.scanLine(i); + src = data + (ii * width) * pixelSize; + for( int j = 0; j < width; ++j, ++dst, src += pixelSize ) { + a = 0; + switch (pixelSize) { + case 4: + a = src[3] ^ 255; + case 3: + r = src[2]; + g = src[1]; + b = src[0]; + break; + case 2: + // todo: do something better + r = g = src[1]; + b = src[0]; + break; + default: + case 1: + r = g = b = src[0]; + break; + } + dst[0] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + FreeImage_CloseMemory(hmem); + return res; + } +} + +static if (USE_LIBPNG) { + + extern (C) void lvpng_error_func (png_structp png, png_const_charp msg) + { + string s = fromStringz(msg); + Log.d("Error while reading PNG image: ", s); + // todo: exceptions do not work inside C function + throw new ImageDecodingException("Error while decoding PNG image"); + } + + extern (C) void lvpng_warning_func (png_structp png, png_const_charp msg) + { + string s = fromStringz(msg); + Log.d("Warn while reading PNG image: ", s); + // todo: exceptions do not work inside C function + throw new ImageDecodingException("Error while decoding PNG image"); + } + + extern (C) void lvpng_read_func(png_structp png, png_bytep buf, png_size_t len) + { + InputStream stream = cast(InputStream)png_get_io_ptr(png); + ubyte[] localbuf = new ubyte[len]; + if (stream.read(localbuf) != len) + throw new ImageDecodingException("Error while reading PNG image"); + for (uint i = 0; i < len; i++) + buf[i] = localbuf[i]; + } + + /// load and decode PNG image + ColorDrawBuf loadPngImage(InputStream stream) + { + png_structp png_ptr = null; + png_infop info_ptr = null; + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + cast(png_voidp)stream, &lvpng_error_func, &lvpng_warning_func); + if ( !png_ptr ) + return null; + + try { + // + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + lvpng_error_func(png_ptr, "cannot create png info struct"); + png_set_read_fn(png_ptr, + cast(void*)stream, &lvpng_read_func); + png_read_info( png_ptr, info_ptr ); + + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + png_get_IHDR(png_ptr, info_ptr, &width, &height, + &bit_depth, &color_type, &interlace_type, + null, null); + ColorDrawBuf drawbuf = new ColorDrawBuf(width, height); + + if (color_type & PNG_COLOR_MASK_PALETTE) + png_set_palette_to_rgb(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png_ptr); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + png_set_invert_alpha(png_ptr); + + if (bit_depth < 8) + png_set_packing(png_ptr); + + png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + int number_passes = png_set_interlace_handling(png_ptr); + png_set_bgr(png_ptr); + + for (int pass = 0; pass < number_passes; pass++) + { + for (int y = 0; y < height; y++) + { + uint * row = drawbuf.scanLine(y); + png_read_rows(png_ptr, cast(ubyte **)&row, null, 1); + } + } + + png_read_end(png_ptr, info_ptr); + + png_destroy_read_struct(&png_ptr, &info_ptr, null); + + return drawbuf; + } catch (ImageDecodingException e) { + if (png_ptr) + { + png_destroy_read_struct(&png_ptr, &info_ptr, null); + } + return null; + } + } + + //bool LVPngImageSource::CheckPattern( const lUInt8 * buf, int ) + //{ + //return( !png_sig_cmp((unsigned char *)buf, (png_size_t)0, 4) ); + //} + } \ No newline at end of file diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index 97165a2b..823c41d2 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -42,6 +42,12 @@ class Window { this() { _backgroundColor = 0xFFFFFF; } + ~this() { + if (_mainWidget !is null) { + destroy(_mainWidget); + _mainWidget = null; + } + } private void animate(Widget root, long interval) { if (root.visibility != Visibility.Visible) diff --git a/src/dlangui/platforms/x11/x11app.d b/src/dlangui/platforms/x11/x11app.d index b409bdf3..13d869ea 100644 --- a/src/dlangui/platforms/x11/x11app.d +++ b/src/dlangui/platforms/x11/x11app.d @@ -17,6 +17,8 @@ version(linux) { import dlangui.graphics.drawbuf; import dlangui.graphics.fonts; import dlangui.graphics.ftfonts; + import dlangui.graphics.images; + import dlangui.widgets.styles; import dlangui.platforms.common.platform; class XCBWindow : Window { @@ -301,6 +303,11 @@ version(linux) { this() { } ~this() { + foreach(ref XCBWindow wnd; _windowMap) { + destroy(wnd); + wnd = null; + } + _windowMap.clear(); disconnect(); } void disconnect() { @@ -522,7 +529,9 @@ version(linux) { FreeTypeFontManager ft = new FreeTypeFontManager(); ft.registerFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", FontFamily.SansSerif, "DejaVu", false, FontWeight.Normal); FontManager.instance = ft; - + + currentTheme = createDefaultTheme(); + XCBPlatform xcb = new XCBPlatform(); if (!xcb.connect()) { return 1; @@ -541,8 +550,15 @@ version(linux) { } Platform.setInstance(null); + Log.d("Destroying XCB platform"); destroy(xcb); + currentTheme = null; + drawableCache = null; + imageCache = null; + FontManager.instance = null; + + Log.d("Exiting main"); return res; } diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 67c0e2d5..b1c60e69 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -2,6 +2,8 @@ module dlangui.widgets.controls; import dlangui.widgets.widget; + + /// static text widget class TextWidget : Widget { this(string ID = null) { @@ -121,7 +123,8 @@ class Button : Widget { Point sz = font.textSize(text); measuredContent(parentWidth, parentHeight, sz.x, sz.y); } - override void onDraw(DrawBuf buf) { + + override void onDraw(DrawBuf buf) { super.onDraw(buf); Rect rc = _pos; applyMargins(rc); @@ -134,26 +137,4 @@ class Button : Widget { font.drawText(buf, rc.left, rc.top, text, textColor); } - override bool onMouseEvent(MouseEvent event) { - if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { - setState(State.Pressed); - Log.d("Button state: ", state); - return true; - } - if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { - resetState(State.Pressed); - Log.d("Button state: ", state); - return true; - } - if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { - resetState(State.Pressed); - return true; - } - if (event.action == MouseAction.FocusIn) { - setState(State.Pressed); - return true; - } - return false; - } - } diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index e6a0ae1a..81605437 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -386,10 +386,29 @@ class Style { return this; } + private static int _instanceCount; this(Theme theme, string id) { _theme = theme; _parentStyle = theme; _id = id; + Log.d("Created style ", _id, ", count=", ++_instanceCount); + } + + ~this() { + foreach(ref Style item; _substates) { + Log.d("Destroying substate"); + destroy(item); + item = null; + } + _substates.clear(); + foreach(ref Style item; _children) { + destroy(item); + item = null; + } + _children.clear(); + _backgroundDrawable.clear(); + _font.clear(); + Log.d("Destroyed style ", _id, ", parentId=", _parentId, ", state=", _stateMask, ", count=", --_instanceCount); } /// create named substyle of this style @@ -403,7 +422,9 @@ class Style { /// create state substyle for this style Style createState(uint stateMask = 0, uint stateValue = 0) { assert(stateMask != 0); - Style child = createSubstyle(null); + Log.d("Creating substate ", stateMask); + Style child = (_theme !is null ? _theme : currentTheme).createSubstyle(null); + child._parentStyle = this; child._stateMask = stateMask; child._stateValue = stateValue; child._backgroundColor = COLOR_UNSPECIFIED; @@ -424,6 +445,7 @@ class Style { } return this; // fallback to current style } + } /// Theme - root for style hierarhy. @@ -447,6 +469,10 @@ class Theme : Style { _layoutHeight = WRAP_CONTENT; _layoutWeight = 1; } + + ~this() { + Log.d("Theme destructor"); + } /// create wrapper style which will have currentTheme.get(id) as parent instead of fixed parent - to modify some base style properties in widget Style modifyStyle(string id) { @@ -499,13 +525,26 @@ class Theme : Style { /// to access current theme private __gshared Theme _currentTheme; @property Theme currentTheme() { return _currentTheme; } +@property void currentTheme(Theme theme) { + if (_currentTheme !is null) { + destroy(_currentTheme); + } + _currentTheme = theme; +} -static this() { - _currentTheme = new Theme("default"); - Style button = _currentTheme.createSubstyle("BUTTON").backgroundImageId("btn_default_small_normal").alignment(Align.Center); - Style text = _currentTheme.createSubstyle("TEXT").margins(Rect(3,3,3,3)).padding(Rect(3,3,3,3)); + +Theme createDefaultTheme() { + Log.d("Creating default theme"); + Theme res = new Theme("default"); + Style button = res.createSubstyle("BUTTON").backgroundImageId("btn_default_small_normal").alignment(Align.Center); + Style text = res.createSubstyle("TEXT").margins(Rect(3,3,3,3)).padding(Rect(3,3,3,3)); button.createState(State.Disabled | State.Focused, State.Disabled | State.Focused).backgroundImageId("btn_default_small_normal_disable_focused"); button.createState(State.Disabled, State.Disabled).backgroundImageId("btn_default_small_normal_disable"); button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed"); button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected"); + return res; } + +shared static ~this() { + currentTheme = null; +} \ No newline at end of file diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index c74f4ad7..a46cd903 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -7,10 +7,14 @@ public import dlangui.graphics.drawbuf; public import dlangui.graphics.images; public import dlangui.graphics.fonts; +public import std.signals; + import dlangui.platforms.common.platform; import std.algorithm; +alias onClick_t = bool delegate(Widget); + /// Visibility (see Android View Visibility) enum Visibility : ubyte { @@ -50,9 +54,18 @@ class Widget { /// window (to be used for top level widgets only!) protected Window _window; + private static int _instanceCount = 0; + /// create widget, with optional id this(string ID = null) { - _id = id; + _id = ID; + Log.d("Created widget, count = ", ++_instanceCount); } + ~this() { + if (_ownStyle !is null) + destroy(_ownStyle); + _ownStyle = null; + Log.d("Destroyed widget, count = ", --_instanceCount); + } /// accessor to style - by lookup in theme by styleId (if style id is not set, theme base style will be used). protected @property const (Style) style() const { @@ -90,7 +103,7 @@ class Widget { } /// returns widget id, null if not set - @property string id() const { return _styleId; } + @property string id() const { return _id; } /// set widget id @property void id(string id) { _id = id; } /// compare widget id with specified value, returs true if matches @@ -266,9 +279,35 @@ class Widget { /// process mouse event; return true if event is processed by widget. bool onMouseEvent(MouseEvent event) { - return false; + // support onClick + if (_onClickListener !is null) { + if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { + setState(State.Pressed); + return true; + } + if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { + resetState(State.Pressed); + _onClickListener(this); + return true; + } + if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { + resetState(State.Pressed); + return true; + } + if (event.action == MouseAction.FocusIn) { + setState(State.Pressed); + return true; + } + } + return false; } + protected onClick_t _onClickListener; + /// on click event listener (bool delegate(Widget)) + @property onClick_t onClickListener() { return _onClickListener; } + /// set on click event listener (bool delegate(Widget)) + @property Widget onClickListener(onClick_t listener) { _onClickListener = listener; return this; } + // ======================================================= // Layout and measurement methods @@ -455,6 +494,7 @@ class Widget { /// sets window (to be used for top level widget from Window implementation). TODO: hide it from API? @property void window(Window window) { _window = window; } + } /// widget list holder