rework resources destroy

This commit is contained in:
Vadim Lopatin 2014-03-18 15:19:20 +04:00
parent 5b358236f2
commit e76394c5da
10 changed files with 574 additions and 434 deletions

View File

@ -43,7 +43,10 @@ extern (C) int UIAppMain(string[] args) {
LinearLayout layout = new LinearLayout(); LinearLayout layout = new LinearLayout();
layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0")); layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0"));
layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); 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(); LinearLayout hlayout = new HorizontalLayout();
//hlayout.addChild((new Button()).text("<<")); //.textColor(0x40FF4000) //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)); vlayout.addChild((new TextWidget()).text("VLayout line 2").textColor(0x40FFFF00));
layout.addChild(vlayout); 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 TextWidget()).textColor(0x40FF4000).text("Text widget"));
layout.addChild((new ImageWidget()).drawableId("exit").padding(Rect(5,5,5,5))); 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 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.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); layout.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT);

View File

@ -670,6 +670,13 @@ class ColorDrawBuf : ColorDrawBufBase {
} }
class Drawable : RefCountedObject { 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); abstract void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0);
@property abstract int width(); @property abstract int width();
@property abstract int height(); @property abstract int height();

View File

@ -146,9 +146,19 @@ struct FontList {
FontRef[] _list; FontRef[] _list;
uint _len; uint _len;
~this() { ~this() {
clear();
}
@property uint length() {
return _len;
}
void clear() {
for (uint i = 0; i < _len; i++) { for (uint i = 0; i < _len; i++) {
_list[i].clear(); _list[i].clear();
_list[i] = null;
} }
_len = 0;
} }
// returns item by index // returns item by index
ref FontRef get(int index) { ref FontRef get(int index) {
@ -216,6 +226,8 @@ class FontManager {
static __gshared FontManager _instance; static __gshared FontManager _instance;
/// sets new font manager singleton instance /// sets new font manager singleton instance
static @property void instance(FontManager manager) { static @property void instance(FontManager manager) {
if (_instance !is null)
destroy(_instance);
_instance = manager; _instance = manager;
} }
/// returns font manager singleton instance /// returns font manager singleton instance
@ -232,5 +244,7 @@ class FontManager {
/// removes entries not used after last call of checkpoint() or cleanup() /// removes entries not used after last call of checkpoint() or cleanup()
abstract void cleanup(); abstract void cleanup();
~this() {} ~this() {
Log.d("Destroying font manager");
}
} }

View File

@ -99,6 +99,7 @@ private class FreeTypeFontFile {
@property int weight() { return _weight; } @property int weight() { return _weight; }
@property bool italic() { return _italic; } @property bool italic() { return _italic; }
private static int _instanceCount;
this(FT_Library library, string filename) { this(FT_Library library, string filename) {
_library = library; _library = library;
_filename = filename; _filename = filename;
@ -106,6 +107,12 @@ private class FreeTypeFontFile {
_matrix.yy = 0x10000; _matrix.yy = 0x10000;
_matrix.xy = 0; _matrix.xy = 0;
_matrix.yx = 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) private static string familyName(FT_Face face)
@ -273,9 +280,6 @@ private class FreeTypeFontFile {
_face = null; _face = null;
} }
~this() {
clear();
}
} }
/** /**
@ -285,22 +289,26 @@ class FreeTypeFont : Font {
private FontFileItem _fontItem; private FontFileItem _fontItem;
private FreeTypeFontFile[] _files; private FreeTypeFontFile[] _files;
static int _instanceCount;
/// need to call create() after construction to initialize font /// need to call create() after construction to initialize font
this(FontFileItem item, int size) { this(FontFileItem item, int size) {
_fontItem = item; _fontItem = item;
_size = size; _size = size;
_height = size; _height = size;
Log.d("Created font, count=", ++_instanceCount);
} }
/// do cleanup
~this() {
clear();
Log.d("Destroyed font, count=", --_instanceCount);
}
private int _size; private int _size;
private int _height; private int _height;
private GlyphCache _glyphCache; private GlyphCache _glyphCache;
/// do cleanup
~this() {
clear();
}
/// cleanup resources /// cleanup resources
override void clear() { override void clear() {
@ -482,6 +490,13 @@ class FreeTypeFontManager : FontManager {
} }
} }
~this() { ~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 // uninit library
if (_library) if (_library)
FT_Done_FreeType(_library); FT_Done_FreeType(_library);

View File

@ -1,61 +1,61 @@
module dlangui.graphics.images; module dlangui.graphics.images;
import dlangui.core.logger; import dlangui.core.logger;
import dlangui.core.types; import dlangui.core.types;
import dlangui.graphics.drawbuf; import dlangui.graphics.drawbuf;
import std.stream; import std.stream;
import std.file; import std.file;
import std.algorithm; import std.algorithm;
import libpng.png; import libpng.png;
/// decoded image cache /// decoded image cache
class ImageCache { class ImageCache {
static class ImageCacheItem { static class ImageCacheItem {
string _filename; string _filename;
DrawBufRef _drawbuf; DrawBufRef _drawbuf;
bool _error; // flag to avoid loading of file if it has been failed once bool _error; // flag to avoid loading of file if it has been failed once
bool _used; bool _used;
this(string filename) { this(string filename) {
_filename = filename; _filename = filename;
} }
@property ref DrawBufRef get() { @property ref DrawBufRef get() {
if (!_drawbuf.isNull || _error) { if (!_drawbuf.isNull || _error) {
_used = true; _used = true;
return _drawbuf; return _drawbuf;
} }
_drawbuf = loadImage(_filename); _drawbuf = loadImage(_filename);
_used = true; _used = true;
if (_drawbuf.isNull) if (_drawbuf.isNull)
_error = true; _error = true;
return _drawbuf; return _drawbuf;
} }
/// remove from memory, will cause reload on next access /// remove from memory, will cause reload on next access
void compact() { void compact() {
if (!_drawbuf.isNull) if (!_drawbuf.isNull)
_drawbuf.clear(); _drawbuf.clear();
} }
/// mark as not used /// mark as not used
void checkpoint() { void checkpoint() {
_used = false; _used = false;
} }
/// cleanup if unused since last checkpoint /// cleanup if unused since last checkpoint
void cleanup() { void cleanup() {
if (!_used) if (!_used)
compact(); compact();
} }
} }
ImageCacheItem[string] _map; ImageCacheItem[string] _map;
/// get and cache image /// get and cache image
ref DrawBufRef get(string filename) { ref DrawBufRef get(string filename) {
if (filename in _map) { if (filename in _map) {
return _map[filename].get; return _map[filename].get;
} }
ImageCacheItem item = new ImageCacheItem(filename); ImageCacheItem item = new ImageCacheItem(filename);
_map[filename] = item; _map[filename] = item;
return item.get; return item.get;
} }
// clear usage flags for all entries // clear usage flags for all entries
void checkpoint() { void checkpoint() {
foreach (item; _map) foreach (item; _map)
@ -73,71 +73,87 @@ class ImageCache {
~this() { ~this() {
Log.i("Destroying ImageCache"); Log.i("Destroying ImageCache");
} }
} }
__gshared ImageCache _imageCache; __gshared ImageCache _imageCache;
/// image cache singleton /// image cache singleton
@property ImageCache imageCache() { return _imageCache; } @property ImageCache imageCache() { return _imageCache; }
/// image cache singleton
__gshared DrawableCache _drawableCache; @property void imageCache(ImageCache cache) {
/// drawable cache singleton if (_imageCache !is null)
@property DrawableCache drawableCache() { return _drawableCache; } destroy(_imageCache);
_imageCache = cache;
shared static this() { }
_imageCache = new ImageCache();
_drawableCache = new DrawableCache(); __gshared DrawableCache _drawableCache;
} /// drawable cache singleton
@property DrawableCache drawableCache() { return _drawableCache; }
class DrawableCache { /// drawable cache singleton
static class DrawableCacheItem { @property void drawableCache(DrawableCache cache) {
string _id; if (_drawableCache !is null)
string _filename; destroy(_drawableCache);
bool _tiled; _drawableCache = cache;
bool _error; }
bool _used;
DrawableRef _drawable; shared static this() {
this(string id, string filename, bool tiled) { _imageCache = new ImageCache();
_id = id; _drawableCache = new DrawableCache();
_filename = filename; }
_tiled = tiled;
_error = filename is null; class DrawableCache {
} static class DrawableCacheItem {
/// remove from memory, will cause reload on next access string _id;
void compact() { string _filename;
if (!_drawable.isNull) bool _tiled;
_drawable.clear(); bool _error;
} bool _used;
/// mark as not used DrawableRef _drawable;
void checkpoint() { this(string id, string filename, bool tiled) {
_used = false; _id = id;
} _filename = filename;
/// cleanup if unused since last checkpoint _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() { void cleanup() {
if (!_used) if (!_used)
compact(); compact();
} }
@property ref DrawableRef drawable() { @property ref DrawableRef drawable() {
_used = true; _used = true;
if (!_drawable.isNull || _error) if (!_drawable.isNull || _error)
return _drawable; return _drawable;
if (_filename !is null) { if (_filename !is null) {
// reload from file // reload from file
DrawBufRef image = imageCache.get(_filename); DrawBufRef image = imageCache.get(_filename);
if (!image.isNull) { if (!image.isNull) {
bool ninePatch = _filename.endsWith(".9.png"); bool ninePatch = _filename.endsWith(".9.png");
_drawable = new ImageDrawable(image, _tiled, ninePatch); _drawable = new ImageDrawable(image, _tiled, ninePatch);
} else } else
_error = true; _error = true;
} }
return _drawable; return _drawable;
} }
} }
void clear() { void clear() {
_idToFileMap.clear(); Log.d("DrawableCache.clear()");
foreach(DrawableCacheItem item; _idToDrawableMap) _idToFileMap.clear();
item.drawable.clear(); foreach(DrawableCacheItem item; _idToDrawableMap)
_idToDrawableMap.clear(); item.drawable.clear();
} _idToDrawableMap.clear();
}
// clear usage flags for all entries // clear usage flags for all entries
void checkpoint() { void checkpoint() {
foreach (item; _idToDrawableMap) foreach (item; _idToDrawableMap)
@ -148,286 +164,286 @@ class DrawableCache {
foreach (item; _idToDrawableMap) foreach (item; _idToDrawableMap)
item.cleanup(); item.cleanup();
} }
string[] _resourcePaths; string[] _resourcePaths;
string[string] _idToFileMap; string[string] _idToFileMap;
DrawableCacheItem[string] _idToDrawableMap; DrawableCacheItem[string] _idToDrawableMap;
ref DrawableRef get(string id) { ref DrawableRef get(string id) {
if (id in _idToDrawableMap) if (id in _idToDrawableMap)
return _idToDrawableMap[id].drawable; return _idToDrawableMap[id].drawable;
string resourceId = id; string resourceId = id;
bool tiled = false; bool tiled = false;
if (id.endsWith(".tiled")) { if (id.endsWith(".tiled")) {
resourceId = id[0..$-6]; // remove .tiled resourceId = id[0..$-6]; // remove .tiled
tiled = true; tiled = true;
} }
string filename = findResource(resourceId); string filename = findResource(resourceId);
DrawableCacheItem item = new DrawableCacheItem(id, filename, tiled); DrawableCacheItem item = new DrawableCacheItem(id, filename, tiled);
_idToDrawableMap[id] = item; _idToDrawableMap[id] = item;
return item.drawable; return item.drawable;
} }
@property string[] resourcePaths() { @property string[] resourcePaths() {
return _resourcePaths; return _resourcePaths;
} }
@property void resourcePaths(string[] paths) { @property void resourcePaths(string[] paths) {
_resourcePaths = paths; _resourcePaths = paths;
clear(); clear();
} }
string findResource(string id) { string findResource(string id) {
if (id in _idToFileMap) if (id in _idToFileMap)
return _idToFileMap[id]; return _idToFileMap[id];
foreach(string path; _resourcePaths) { foreach(string path; _resourcePaths) {
char[] name = path.dup; char[] name = path.dup;
name ~= id; name ~= id;
name ~= ".png"; name ~= ".png";
if (!exists(name)) { if (!exists(name)) {
name = path.dup; name = path.dup;
name ~= id; name ~= id;
name ~= ".9.png"; name ~= ".9.png";
} }
if (exists(name) && isFile(name)) { if (exists(name) && isFile(name)) {
string filename = name.dup; string filename = name.dup;
_idToFileMap[id] = filename; _idToFileMap[id] = filename;
return filename; return filename;
} }
} }
return null; return null;
} }
this() { this() {
Log.i("Creating DrawableCache"); Log.i("Creating DrawableCache");
} }
~this() { ~this() {
Log.i("Destroying DrawableCache"); Log.i("Destroying DrawableCache");
} }
} }
/// load and decode image from file to ColorDrawBuf, returns null if loading or decoding is failed /// load and decode image from file to ColorDrawBuf, returns null if loading or decoding is failed
ColorDrawBuf loadImage(string filename) { ColorDrawBuf loadImage(string filename) {
Log.d("Loading image from file " ~ filename); Log.d("Loading image from file " ~ filename);
try { try {
std.stream.File f = new std.stream.File(filename); std.stream.File f = new std.stream.File(filename);
scope(exit) { f.close(); } scope(exit) { f.close(); }
return loadImage(f); return loadImage(f);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
/// load and decode image from stream to ColorDrawBuf, returns null if loading or decoding is failed /// load and decode image from stream to ColorDrawBuf, returns null if loading or decoding is failed
ColorDrawBuf loadImage(InputStream stream) { ColorDrawBuf loadImage(InputStream stream) {
if (stream is null || !stream.isOpen) if (stream is null || !stream.isOpen)
return null; return null;
static if (USE_FREEIMAGE) { static if (USE_FREEIMAGE) {
return loadFreeImage(stream); return loadFreeImage(stream);
} else static if (USE_LIBPNG) { } else static if (USE_LIBPNG) {
return loadPngImage(stream); return loadPngImage(stream);
} }
} }
class ImageDecodingException : Exception { class ImageDecodingException : Exception {
this(string msg) { this(string msg) {
super(msg); super(msg);
} }
} }
immutable bool USE_LIBPNG = false; immutable bool USE_LIBPNG = false;
immutable bool USE_FREEIMAGE = true; immutable bool USE_FREEIMAGE = true;
shared static this() { shared static this() {
//import derelict.freeimage.freeimage; //import derelict.freeimage.freeimage;
//DerelictFI.load(); //DerelictFI.load();
} }
static if (USE_FREEIMAGE) { static if (USE_FREEIMAGE) {
ColorDrawBuf loadFreeImage(InputStream stream) { ColorDrawBuf loadFreeImage(InputStream stream) {
import derelict.freeimage.freeimage; import derelict.freeimage.freeimage;
static bool FREE_IMAGE_LOADED; static bool FREE_IMAGE_LOADED;
if (!FREE_IMAGE_LOADED) { if (!FREE_IMAGE_LOADED) {
DerelictFI.load(); DerelictFI.load();
FREE_IMAGE_LOADED = true; FREE_IMAGE_LOADED = true;
} }
ubyte imagebuf[]; ubyte imagebuf[];
ubyte readbuf[4096]; ubyte readbuf[4096];
for (;;) { for (;;) {
size_t bytesRead = stream.read(readbuf); size_t bytesRead = stream.read(readbuf);
if (!bytesRead) if (!bytesRead)
break; break;
imagebuf ~= readbuf[0..bytesRead]; imagebuf ~= readbuf[0..bytesRead];
} }
//pointer to the image, once loaded //pointer to the image, once loaded
FIBITMAP *dib = null; //image format FIBITMAP *dib = null; //image format
FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
// attach the binary data to a memory stream // attach the binary data to a memory stream
FIMEMORY *hmem = FreeImage_OpenMemory(imagebuf.ptr, imagebuf.length); FIMEMORY *hmem = FreeImage_OpenMemory(imagebuf.ptr, imagebuf.length);
fif = FreeImage_GetFileTypeFromMemory(hmem); fif = FreeImage_GetFileTypeFromMemory(hmem);
//check that the plugin has reading capabilities and load the file //check that the plugin has reading capabilities and load the file
if(!FreeImage_FIFSupportsReading(fif)) { if(!FreeImage_FIFSupportsReading(fif)) {
FreeImage_CloseMemory(hmem); FreeImage_CloseMemory(hmem);
return null; return null;
} }
// load an image from the memory stream // load an image from the memory stream
dib = FreeImage_LoadFromMemory(fif, hmem, 0); dib = FreeImage_LoadFromMemory(fif, hmem, 0);
//if the image failed to load, return failure //if the image failed to load, return failure
if (!dib) { if (!dib) {
Log.e("Failed to decode image"); Log.e("Failed to decode image");
FreeImage_CloseMemory(hmem); FreeImage_CloseMemory(hmem);
return null; return null;
} }
//retrieve the image data //retrieve the image data
ubyte * data = cast(ubyte*)FreeImage_GetBits(dib); ubyte * data = cast(ubyte*)FreeImage_GetBits(dib);
//get the image width and height, and size per pixel //get the image width and height, and size per pixel
int width = FreeImage_GetWidth(dib); int width = FreeImage_GetWidth(dib);
int height = FreeImage_GetHeight(dib); int height = FreeImage_GetHeight(dib);
int pixelSize = FreeImage_GetBPP(dib)/8; int pixelSize = FreeImage_GetBPP(dib)/8;
int size = width*height*pixelSize; int size = width*height*pixelSize;
ColorDrawBuf res = new ColorDrawBuf(width, height); ColorDrawBuf res = new ColorDrawBuf(width, height);
//swap R and B and invert image while copying //swap R and B and invert image while copying
ubyte* src; ubyte* src;
uint* dst; uint* dst;
uint r, g, b, a; uint r, g, b, a;
for( int i = 0, ii = height-1; i < height ; ++i, --ii ) { for( int i = 0, ii = height-1; i < height ; ++i, --ii ) {
dst = res.scanLine(i); dst = res.scanLine(i);
src = data + (ii * width) * pixelSize; src = data + (ii * width) * pixelSize;
for( int j = 0; j < width; ++j, ++dst, src += pixelSize ) { for( int j = 0; j < width; ++j, ++dst, src += pixelSize ) {
a = 0; a = 0;
switch (pixelSize) { switch (pixelSize) {
case 4: case 4:
a = src[3] ^ 255; a = src[3] ^ 255;
case 3: case 3:
r = src[2]; r = src[2];
g = src[1]; g = src[1];
b = src[0]; b = src[0];
break; break;
case 2: case 2:
// todo: do something better // todo: do something better
r = g = src[1]; r = g = src[1];
b = src[0]; b = src[0];
break; break;
default: default:
case 1: case 1:
r = g = b = src[0]; r = g = b = src[0];
break; break;
} }
dst[0] = (a << 24) | (r << 16) | (g << 8) | b; dst[0] = (a << 24) | (r << 16) | (g << 8) | b;
} }
} }
FreeImage_CloseMemory(hmem); FreeImage_CloseMemory(hmem);
return res; return res;
} }
} }
static if (USE_LIBPNG) { static if (USE_LIBPNG) {
extern (C) void lvpng_error_func (png_structp png, png_const_charp msg) extern (C) void lvpng_error_func (png_structp png, png_const_charp msg)
{ {
string s = fromStringz(msg); string s = fromStringz(msg);
Log.d("Error while reading PNG image: ", s); Log.d("Error while reading PNG image: ", s);
// todo: exceptions do not work inside C function // todo: exceptions do not work inside C function
throw new ImageDecodingException("Error while decoding PNG image"); throw new ImageDecodingException("Error while decoding PNG image");
} }
extern (C) void lvpng_warning_func (png_structp png, png_const_charp msg) extern (C) void lvpng_warning_func (png_structp png, png_const_charp msg)
{ {
string s = fromStringz(msg); string s = fromStringz(msg);
Log.d("Warn while reading PNG image: ", s); Log.d("Warn while reading PNG image: ", s);
// todo: exceptions do not work inside C function // todo: exceptions do not work inside C function
throw new ImageDecodingException("Error while decoding PNG image"); throw new ImageDecodingException("Error while decoding PNG image");
} }
extern (C) void lvpng_read_func(png_structp png, png_bytep buf, png_size_t len) 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); InputStream stream = cast(InputStream)png_get_io_ptr(png);
ubyte[] localbuf = new ubyte[len]; ubyte[] localbuf = new ubyte[len];
if (stream.read(localbuf) != len) if (stream.read(localbuf) != len)
throw new ImageDecodingException("Error while reading PNG image"); throw new ImageDecodingException("Error while reading PNG image");
for (uint i = 0; i < len; i++) for (uint i = 0; i < len; i++)
buf[i] = localbuf[i]; buf[i] = localbuf[i];
} }
/// load and decode PNG image /// load and decode PNG image
ColorDrawBuf loadPngImage(InputStream stream) ColorDrawBuf loadPngImage(InputStream stream)
{ {
png_structp png_ptr = null; png_structp png_ptr = null;
png_infop info_ptr = null; png_infop info_ptr = null;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
cast(png_voidp)stream, &lvpng_error_func, &lvpng_warning_func); cast(png_voidp)stream, &lvpng_error_func, &lvpng_warning_func);
if ( !png_ptr ) if ( !png_ptr )
return null; return null;
try { try {
// //
info_ptr = png_create_info_struct(png_ptr); info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
lvpng_error_func(png_ptr, "cannot create png info struct"); lvpng_error_func(png_ptr, "cannot create png info struct");
png_set_read_fn(png_ptr, png_set_read_fn(png_ptr,
cast(void*)stream, &lvpng_read_func); cast(void*)stream, &lvpng_read_func);
png_read_info( png_ptr, info_ptr ); png_read_info( png_ptr, info_ptr );
png_uint_32 width, height; png_uint_32 width, height;
int bit_depth, color_type, interlace_type; int bit_depth, color_type, interlace_type;
png_get_IHDR(png_ptr, info_ptr, &width, &height, png_get_IHDR(png_ptr, info_ptr, &width, &height,
&bit_depth, &color_type, &interlace_type, &bit_depth, &color_type, &interlace_type,
null, null); null, null);
ColorDrawBuf drawbuf = new ColorDrawBuf(width, height); ColorDrawBuf drawbuf = new ColorDrawBuf(width, height);
if (color_type & PNG_COLOR_MASK_PALETTE) if (color_type & PNG_COLOR_MASK_PALETTE)
png_set_palette_to_rgb(png_ptr); png_set_palette_to_rgb(png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr); png_set_expand_gray_1_2_4_to_8(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png_ptr); png_set_tRNS_to_alpha(png_ptr);
if (bit_depth == 16) if (bit_depth == 16)
png_set_strip_16(png_ptr); png_set_strip_16(png_ptr);
png_set_invert_alpha(png_ptr); png_set_invert_alpha(png_ptr);
if (bit_depth < 8) if (bit_depth < 8)
png_set_packing(png_ptr); png_set_packing(png_ptr);
png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png_ptr); png_set_gray_to_rgb(png_ptr);
int number_passes = png_set_interlace_handling(png_ptr); int number_passes = png_set_interlace_handling(png_ptr);
png_set_bgr(png_ptr); png_set_bgr(png_ptr);
for (int pass = 0; pass < number_passes; pass++) for (int pass = 0; pass < number_passes; pass++)
{ {
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
uint * row = drawbuf.scanLine(y); uint * row = drawbuf.scanLine(y);
png_read_rows(png_ptr, cast(ubyte **)&row, null, 1); png_read_rows(png_ptr, cast(ubyte **)&row, null, 1);
} }
} }
png_read_end(png_ptr, info_ptr); png_read_end(png_ptr, info_ptr);
png_destroy_read_struct(&png_ptr, &info_ptr, null); png_destroy_read_struct(&png_ptr, &info_ptr, null);
return drawbuf; return drawbuf;
} catch (ImageDecodingException e) { } catch (ImageDecodingException e) {
if (png_ptr) if (png_ptr)
{ {
png_destroy_read_struct(&png_ptr, &info_ptr, null); png_destroy_read_struct(&png_ptr, &info_ptr, null);
} }
return null; return null;
} }
} }
//bool LVPngImageSource::CheckPattern( const lUInt8 * buf, int ) //bool LVPngImageSource::CheckPattern( const lUInt8 * buf, int )
//{ //{
//return( !png_sig_cmp((unsigned char *)buf, (png_size_t)0, 4) ); //return( !png_sig_cmp((unsigned char *)buf, (png_size_t)0, 4) );
//} //}
} }

View File

@ -42,6 +42,12 @@ class Window {
this() { this() {
_backgroundColor = 0xFFFFFF; _backgroundColor = 0xFFFFFF;
} }
~this() {
if (_mainWidget !is null) {
destroy(_mainWidget);
_mainWidget = null;
}
}
private void animate(Widget root, long interval) { private void animate(Widget root, long interval) {
if (root.visibility != Visibility.Visible) if (root.visibility != Visibility.Visible)

View File

@ -17,6 +17,8 @@ version(linux) {
import dlangui.graphics.drawbuf; import dlangui.graphics.drawbuf;
import dlangui.graphics.fonts; import dlangui.graphics.fonts;
import dlangui.graphics.ftfonts; import dlangui.graphics.ftfonts;
import dlangui.graphics.images;
import dlangui.widgets.styles;
import dlangui.platforms.common.platform; import dlangui.platforms.common.platform;
class XCBWindow : Window { class XCBWindow : Window {
@ -301,6 +303,11 @@ version(linux) {
this() { this() {
} }
~this() { ~this() {
foreach(ref XCBWindow wnd; _windowMap) {
destroy(wnd);
wnd = null;
}
_windowMap.clear();
disconnect(); disconnect();
} }
void disconnect() { void disconnect() {
@ -522,7 +529,9 @@ version(linux) {
FreeTypeFontManager ft = new FreeTypeFontManager(); FreeTypeFontManager ft = new FreeTypeFontManager();
ft.registerFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", FontFamily.SansSerif, "DejaVu", false, FontWeight.Normal); ft.registerFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", FontFamily.SansSerif, "DejaVu", false, FontWeight.Normal);
FontManager.instance = ft; FontManager.instance = ft;
currentTheme = createDefaultTheme();
XCBPlatform xcb = new XCBPlatform(); XCBPlatform xcb = new XCBPlatform();
if (!xcb.connect()) { if (!xcb.connect()) {
return 1; return 1;
@ -541,8 +550,15 @@ version(linux) {
} }
Platform.setInstance(null); Platform.setInstance(null);
Log.d("Destroying XCB platform");
destroy(xcb); destroy(xcb);
currentTheme = null;
drawableCache = null;
imageCache = null;
FontManager.instance = null;
Log.d("Exiting main");
return res; return res;
} }

View File

@ -2,6 +2,8 @@ module dlangui.widgets.controls;
import dlangui.widgets.widget; import dlangui.widgets.widget;
/// static text widget /// static text widget
class TextWidget : Widget { class TextWidget : Widget {
this(string ID = null) { this(string ID = null) {
@ -121,7 +123,8 @@ class Button : Widget {
Point sz = font.textSize(text); Point sz = font.textSize(text);
measuredContent(parentWidth, parentHeight, sz.x, sz.y); measuredContent(parentWidth, parentHeight, sz.x, sz.y);
} }
override void onDraw(DrawBuf buf) {
override void onDraw(DrawBuf buf) {
super.onDraw(buf); super.onDraw(buf);
Rect rc = _pos; Rect rc = _pos;
applyMargins(rc); applyMargins(rc);
@ -134,26 +137,4 @@ class Button : Widget {
font.drawText(buf, rc.left, rc.top, text, textColor); 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;
}
} }

View File

@ -386,10 +386,29 @@ class Style {
return this; return this;
} }
private static int _instanceCount;
this(Theme theme, string id) { this(Theme theme, string id) {
_theme = theme; _theme = theme;
_parentStyle = theme; _parentStyle = theme;
_id = id; _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 /// create named substyle of this style
@ -403,7 +422,9 @@ class Style {
/// create state substyle for this style /// create state substyle for this style
Style createState(uint stateMask = 0, uint stateValue = 0) { Style createState(uint stateMask = 0, uint stateValue = 0) {
assert(stateMask != 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._stateMask = stateMask;
child._stateValue = stateValue; child._stateValue = stateValue;
child._backgroundColor = COLOR_UNSPECIFIED; child._backgroundColor = COLOR_UNSPECIFIED;
@ -424,6 +445,7 @@ class Style {
} }
return this; // fallback to current style return this; // fallback to current style
} }
} }
/// Theme - root for style hierarhy. /// Theme - root for style hierarhy.
@ -447,6 +469,10 @@ class Theme : Style {
_layoutHeight = WRAP_CONTENT; _layoutHeight = WRAP_CONTENT;
_layoutWeight = 1; _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 /// 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) { Style modifyStyle(string id) {
@ -499,13 +525,26 @@ class Theme : Style {
/// to access current theme /// to access current theme
private __gshared Theme _currentTheme; private __gshared Theme _currentTheme;
@property Theme currentTheme() { return _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"); Theme createDefaultTheme() {
Style button = _currentTheme.createSubstyle("BUTTON").backgroundImageId("btn_default_small_normal").alignment(Align.Center); Log.d("Creating default theme");
Style text = _currentTheme.createSubstyle("TEXT").margins(Rect(3,3,3,3)).padding(Rect(3,3,3,3)); 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.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.Disabled, State.Disabled).backgroundImageId("btn_default_small_normal_disable");
button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed"); button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed");
button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected"); button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected");
return res;
} }
shared static ~this() {
currentTheme = null;
}

View File

@ -7,10 +7,14 @@ public import dlangui.graphics.drawbuf;
public import dlangui.graphics.images; public import dlangui.graphics.images;
public import dlangui.graphics.fonts; public import dlangui.graphics.fonts;
public import std.signals;
import dlangui.platforms.common.platform; import dlangui.platforms.common.platform;
import std.algorithm; import std.algorithm;
alias onClick_t = bool delegate(Widget);
/// Visibility (see Android View Visibility) /// Visibility (see Android View Visibility)
enum Visibility : ubyte { enum Visibility : ubyte {
@ -50,9 +54,18 @@ class Widget {
/// window (to be used for top level widgets only!) /// window (to be used for top level widgets only!)
protected Window _window; protected Window _window;
private static int _instanceCount = 0;
/// create widget, with optional id
this(string ID = null) { 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). /// 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 { protected @property const (Style) style() const {
@ -90,7 +103,7 @@ class Widget {
} }
/// returns widget id, null if not set /// returns widget id, null if not set
@property string id() const { return _styleId; } @property string id() const { return _id; }
/// set widget id /// set widget id
@property void id(string id) { _id = id; } @property void id(string id) { _id = id; }
/// compare widget id with specified value, returs true if matches /// 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. /// process mouse event; return true if event is processed by widget.
bool onMouseEvent(MouseEvent event) { 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 // 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? /// sets window (to be used for top level widget from Window implementation). TODO: hide it from API?
@property void window(Window window) { _window = window; } @property void window(Window window) { _window = window; }
} }
/// widget list holder /// widget list holder