diff --git a/examples/example1/winmain.d b/examples/example1/winmain.d index a83e2575..69a85ede 100644 --- a/examples/example1/winmain.d +++ b/examples/example1/winmain.d @@ -55,6 +55,10 @@ extern (C) int UIAppMain(string[] args) { Log.d("Some debug message"); Log.e("Sample error #", 22); + string[] imageDirs = [ + resourceDir + ]; + drawableCache.resourcePaths = imageDirs; Window window = Platform.instance().createWindow("My Window", null); Widget myWidget = (new TextWidget()).textColor(0x40FF4000); myWidget.text = "Some strange text string. 1234567890"; diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 272ab323..411e98d4 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -1,5 +1,7 @@ module dlangui.core.types; +import std.algorithm; + struct Point { int x; int y; @@ -136,3 +138,14 @@ wstring fromWStringz(const(wchar[]) s) { return cast(wstring)(s[0..i].dup); } +bool startsWith(string str, string prefix) { + if (str.length >= prefix.length) + return equal(str[0..prefix.length], prefix); + return false; +} + +bool endsWith(string str, string suffix) { + if (str.length >= suffix.length) + return equal(str[$-suffix.length .. $], suffix); + return false; +} diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index 0368aaaa..a571ac78 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -1,6 +1,7 @@ module dlangui.graphics.drawbuf; public import dlangui.core.types; +import dlangui.core.logger; /// blend two RGB pixels using alpha uint blendARGB(uint dst, uint src, uint alpha) { @@ -244,7 +245,7 @@ class ColorDrawBufBase : DrawBuf { int sd = src1 - src0; int[] res = new int[dd]; for (int i = 0; i < dd; i++) - res[i] = src0 + i * dd / sd; + res[i] = src0 + i * sd / dd; return res; } /// draw source buffer rectangle contents to destination buffer rectangle applying rescaling @@ -403,7 +404,7 @@ class ColorDrawBuf : ColorDrawBufBase { } } -class Drawable { +class Drawable : RefCountedObject { abstract void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0); @property abstract int width(); @property abstract int height(); @@ -465,3 +466,5 @@ class ImageDrawable : Drawable { } } } + +alias DrawableRef = Ref!Drawable; diff --git a/src/dlangui/graphics/images.d b/src/dlangui/graphics/images.d index 96d446a0..490ecab4 100644 --- a/src/dlangui/graphics/images.d +++ b/src/dlangui/graphics/images.d @@ -1,8 +1,10 @@ module dlangui.graphics.images; import dlangui.core.logger; +import dlangui.core.types; import dlangui.graphics.drawbuf; import std.stream; +import std.file; import libpng.png; /// decoded image cache @@ -72,6 +74,130 @@ class ImageCache { } } +__gshared ImageCache _imageCache; +/// image cache singleton +@property ImageCache imageCache() { return _imageCache; } + +__gshared DrawableCache _drawableCache; +/// drawable cache singleton +@property DrawableCache drawableCache() { return _drawableCache; } + +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 + 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) + _drawable = new ImageDrawable(image, _tiled); + else + _error = true; + } + return _drawable; + } + } + void clear() { + _idToFileMap.clear(); + foreach(DrawableCacheItem item; _idToDrawableMap) + item.drawable.clear(); + _idToDrawableMap.clear(); + } + // clear usage flags for all entries + void checkpoint() { + foreach (item; _idToDrawableMap) + item.checkpoint(); + } + // removes entries not used after last call of checkpoint() or cleanup() + void cleanup() { + 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; + } + 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); diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index ad6f85a1..e5721139 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -3,6 +3,7 @@ module dlangui.widgets.widget; public import dlangui.core.types; public import dlangui.widgets.styles; public import dlangui.graphics.drawbuf; +public import dlangui.graphics.images; public import dlangui.graphics.fonts; import dlangui.platforms.common.platform; @@ -151,6 +152,9 @@ class Widget { applyPadding(rc); buf.fillRect(Rect(rc.left + rc.width / 2, rc.top, rc.left + rc.width / 2 + 2, rc.bottom), 0xFF8000); buf.fillRect(Rect(rc.left, rc.top + rc.height / 2, rc.right, rc.top + rc.height / 2 + 2), 0xFF80FF); + DrawableRef img = drawableCache.get("exit"); + if (!img.isNull) + img.drawTo(buf, Rect(50, 50, 50+64, 50+64)); _needDraw = false; } /// Applies alignment for content of size sz - set rectangle rc to aligned value of content inside of initial value of rc.