diff --git a/dlanguilib.visualdproj b/dlanguilib.visualdproj index c6afebba..e36b0deb 100644 --- a/dlanguilib.visualdproj +++ b/dlanguilib.visualdproj @@ -312,6 +312,7 @@ + diff --git a/examples/example1/res/tab_btn_up.xml b/examples/example1/res/tab_btn_up.xml index 9e16cf29..bcae788c 100644 --- a/examples/example1/res/tab_btn_up.xml +++ b/examples/example1/res/tab_btn_up.xml @@ -2,7 +2,7 @@ diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 3a82229b..de72071c 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -193,3 +193,20 @@ wstring fromWStringz(const(wchar[]) s) { return cast(wstring)(s[0..i].dup); } + +/// widget state flags - bits +enum State : uint { + /// state not specified / normal + Normal = 0, + Pressed = 1, + Focused = 2, + Enabled = 4, + Hovered = 8, // mouse pointer is over control, buttons not pressed + Selected = 16, + Checkable = 32, + Checked = 64, + Activated = 128, + WindowFocused = 256, + Parent = 0x10000, // use parent's state +} + diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index abb8db38..593ef533 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -679,292 +679,4 @@ 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, uint state = 0, int tilex0 = 0, int tiley0 = 0); - @property abstract int width(); - @property abstract int height(); - @property Rect padding() { return Rect(0,0,0,0); } -} -class EmptyDrawable : Drawable { - override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { - } - @property override int width() { return 0; } - @property override int height() { return 0; } -} - -class SolidFillDrawable : Drawable { - protected uint _color; - this(uint color) { - _color = color; - } - override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { - if ((_color >> 24) != 0xFF) // not fully transparent - buf.fillRect(rc, _color); - } - @property override int width() { return 1; } - @property override int height() { return 1; } -} - -class ImageDrawable : Drawable { - protected DrawBufRef _image; - protected bool _tiled; - //private int _instanceCount; - this(ref DrawBufRef image, bool tiled = false, bool ninePatch = false) { - _image = image; - _tiled = tiled; - if (ninePatch) - _image.detectNinePatch(); - //Log.d("Created ImageDrawable, count=", ++_instanceCount); - } - ~this() { - _image.clear(); - //Log.d("Destroyed ImageDrawable, count=", --_instanceCount); - } - @property override int width() { - if (_image.isNull) - return 0; - if (_image.hasNinePatch) - return _image.width - 2; - return _image.width; - } - @property override int height() { - if (_image.isNull) - return 0; - if (_image.hasNinePatch) - return _image.height - 2; - return _image.height; - } - @property override Rect padding() { - if (!_image.isNull && _image.hasNinePatch) - return _image.ninePatch.padding; - return Rect(0,0,0,0); - } - private static void correctFrameBounds(ref int n1, ref int n2, ref int n3, ref int n4) { - if (n1 > n2) { - //assert(n2 - n1 == n4 - n3); - int middledist = (n1 + n2) / 2 - n1; - n1 = n2 = n1 + middledist; - n3 = n4 = n3 + middledist; - } - } - override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { - if (_image.isNull) - return; - if (_image.hasNinePatch) { - // draw nine patch - const NinePatch * p = _image.ninePatch; - //Log.d("drawing nine patch image with frame ", p.frame, " padding ", p.padding); - int w = width; - int h = height; - Rect dstrect = rc; - Rect srcrect = Rect(1, 1, w + 1, h + 1); - if (true) { //buf.applyClipping(dstrect, srcrect)) { - int x0 = srcrect.left; - int x1 = srcrect.left + p.frame.left; - int x2 = srcrect.right - p.frame.right; - int x3 = srcrect.right; - int y0 = srcrect.top; - int y1 = srcrect.top + p.frame.top; - int y2 = srcrect.bottom - p.frame.bottom; - int y3 = srcrect.bottom; - int dstx0 = rc.left; - int dstx1 = rc.left + p.frame.left; - int dstx2 = rc.right - p.frame.right; - int dstx3 = rc.right; - int dsty0 = rc.top; - int dsty1 = rc.top + p.frame.top; - int dsty2 = rc.bottom - p.frame.bottom; - int dsty3 = rc.bottom; - //Log.d("x bounds: ", x0, ", ", x1, ", ", x2, ", ", x3, " dst ", dstx0, ", ", dstx1, ", ", dstx2, ", ", dstx3); - //Log.d("y bounds: ", y0, ", ", y1, ", ", y2, ", ", y3, " dst ", dsty0, ", ", dsty1, ", ", dsty2, ", ", dsty3); - - correctFrameBounds(x1, x2, dstx1, dstx2); - correctFrameBounds(y1, y2, dsty1, dsty2); - - //correctFrameBounds(x1, x2); - //correctFrameBounds(y1, y2); - //correctFrameBounds(dstx1, dstx2); - //correctFrameBounds(dsty1, dsty2); - if (y0 < y1 && dsty0 < dsty1) { - // top row - if (x0 < x1 && dstx0 < dstx1) - buf.drawFragment(dstx0, dsty0, _image.get, Rect(x0, y0, x1, y1)); // top left - if (x1 < x2 && dstx1 < dstx2) - buf.drawRescaled(Rect(dstx1, dsty0, dstx2, dsty1), _image.get, Rect(x1, y0, x2, y1)); // top center - if (x2 < x3 && dstx2 < dstx3) - buf.drawFragment(dstx2, dsty0, _image.get, Rect(x2, y0, x3, y1)); // top right - } - if (y1 < y2 && dsty1 < dsty2) { - // middle row - if (x0 < x1 && dstx0 < dstx1) - buf.drawRescaled(Rect(dstx0, dsty1, dstx1, dsty2), _image.get, Rect(x0, y1, x1, y2)); // middle center - if (x1 < x2 && dstx1 < dstx2) - buf.drawRescaled(Rect(dstx1, dsty1, dstx2, dsty2), _image.get, Rect(x1, y1, x2, y2)); // center - if (x2 < x3 && dstx2 < dstx3) - buf.drawRescaled(Rect(dstx2, dsty1, dstx3, dsty2), _image.get, Rect(x2, y1, x3, y2)); // middle center - } - if (y2 < y3 && dsty2 < dsty3) { - // bottom row - if (x0 < x1 && dstx0 < dstx1) - buf.drawFragment(dstx0, dsty2, _image.get, Rect(x0, y2, x1, y3)); // bottom left - if (x1 < x2 && dstx1 < dstx2) - buf.drawRescaled(Rect(dstx1, dsty2, dstx2, dsty3), _image.get, Rect(x1, y2, x2, y3)); // bottom center - if (x2 < x3 && dstx2 < dstx3) - buf.drawFragment(dstx2, dsty2, _image.get, Rect(x2, y2, x3, y3)); // bottom right - } - } - } else if (_tiled) { - // tiled - } else { - // rescaled or normal - if (rc.width != _image.width || rc.height != _image.height) - buf.drawRescaled(rc, _image.get, Rect(0, 0, _image.width, _image.height)); - else - buf.drawImage(rc.left, rc.top, _image); - } - } -} - -import std.xml; -import std.algorithm; -import dlangui.widgets.styles; - -void extractStateFlag(ref string[string] attr, string attrName, State state, ref uint stateMask, ref uint stateValue) { - if (attrName in attr) { - string value = attr[attrName]; - if (value.equal("true")) - stateValue |= state; - stateMask |= state; - } -} - -/// converts XML attribute name to State (see http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) -void extractStateFlags(ref string[string] attr, ref uint stateMask, ref uint stateValue) { - extractStateFlag(attr, "state_pressed", State.Pressed, stateMask, stateValue); - extractStateFlag(attr, "state_focused", State.Focused, stateMask, stateValue); - extractStateFlag(attr, "state_hovered", State.Hovered, stateMask, stateValue); - extractStateFlag(attr, "state_selected", State.Selected, stateMask, stateValue); - extractStateFlag(attr, "state_checkable", State.Checkable, stateMask, stateValue); - extractStateFlag(attr, "state_checked", State.Checked, stateMask, stateValue); - extractStateFlag(attr, "state_enabled", State.Enabled, stateMask, stateValue); - extractStateFlag(attr, "state_activated", State.Activated, stateMask, stateValue); - extractStateFlag(attr, "state_window_focused", State.WindowFocused, stateMask, stateValue); -} - -/* -sample: -(prefix android: is optional) - - - - - -*/ - -/// Drawable which is drawn depending on state (see http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) -class StateDrawable : Drawable { - - static struct StateItem { - uint stateMask; - uint stateValue; - DrawableRef drawable; - @property bool matchState(uint state) { - return (stateMask & state) == stateValue; - } - } - protected StateItem[] _stateList; - - void addState(uint stateMask, uint stateValue, string resourceId) { - StateItem item; - item.stateMask = stateMask; - item.stateValue = stateValue; - item.drawable = dlangui.graphics.images.drawableCache.get(resourceId); - _stateList ~= item; - } - - void addState(uint stateMask, uint stateValue, DrawableRef drawable) { - StateItem item; - item.stateMask = stateMask; - item.stateValue = stateValue; - item.drawable = drawable; - _stateList ~= item; - } - - bool load(Element element) { - foreach(item; element.elements) { - if (item.tag.name.equal("item")) { - if ("drawable" in item.tag.attr) { - string drawableId = item.tag.attr["drawable"]; - uint stateMask, stateValue; - extractStateFlags(item.tag.attr, stateMask, stateValue); - if (drawableId !is null) { - addState(stateMask, stateValue, drawableId); - } - } - } - } - return _stateList.length > 0; - } - - /// load from XML file - bool load(string filename) { - import std.file; - import std.string; - - try { - string s = cast(string)std.file.read(filename); - - // Check for well-formedness - check(s); - - // Make a DOM tree - auto doc = new Document(s); - - return load(doc); - } catch (Exception e) { - Log.e("Cannot read drawable resource from file ", filename); - return false; - } - } - - override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { - foreach(ref item; _stateList) - if (item.matchState(state)) { - item.drawable.drawTo(buf, rc, state, tilex0, tiley0); - return; - } - } - - @property override int width() { - return (_stateList.length > 0) ? _stateList[0].drawable.width : 0; - } - @property override int height() { - return (_stateList.length > 0) ? _stateList[0].drawable.height : 0; - } - @property override Rect padding() { - return (_stateList.length > 0) ? _stateList[0].drawable.padding : Rect(0,0,0,0); - } -} - -alias DrawableRef = Ref!Drawable; diff --git a/src/dlangui/graphics/images.d b/src/dlangui/graphics/images.d index 7d6bc536..5681a95f 100644 --- a/src/dlangui/graphics/images.d +++ b/src/dlangui/graphics/images.d @@ -4,231 +4,8 @@ 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; - } - // clear usage flags for all entries - void checkpoint() { - foreach (item; _map) - item.checkpoint(); - } - // removes entries not used after last call of checkpoint() or cleanup() - void cleanup() { - foreach (item; _map) - item.cleanup(); - } - - this() { - Log.i("Creating ImageCache"); - } - ~this() { - Log.i("Destroying ImageCache"); - foreach (ref item; _map) { - destroy(item); - item = null; - } - _map.clear(); - } -} - -__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; - //private int _instanceCount; - this(string id, string filename, bool tiled) { - _id = id; - _filename = filename; - _tiled = tiled; - _error = filename is null; - //Log.d("Created DrawableCacheItem, count=", ++_instanceCount); - } - ~this() { - _drawable.clear(); - //Log.d("Destroyed DrawableCacheItem, count=", --_instanceCount); - } - /// 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() { - 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) - 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"); - foreach (ref item; _idToDrawableMap) { - destroy(item); - item = null; - } - _idToDrawableMap.clear(); - } -} - /// 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/graphics/resources.d b/src/dlangui/graphics/resources.d new file mode 100644 index 00000000..6609d819 --- /dev/null +++ b/src/dlangui/graphics/resources.d @@ -0,0 +1,562 @@ +module dlangui.graphics.resources; + +import dlangui.graphics.images; +import dlangui.graphics.drawbuf; +import dlangui.core.logger; +import std.file; +import std.algorithm; +import std.xml; +import std.algorithm; + + +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, uint state = 0, int tilex0 = 0, int tiley0 = 0); + @property abstract int width(); + @property abstract int height(); + @property Rect padding() { return Rect(0,0,0,0); } +} + +class EmptyDrawable : Drawable { + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + } + @property override int width() { return 0; } + @property override int height() { return 0; } +} + +class SolidFillDrawable : Drawable { + protected uint _color; + this(uint color) { + _color = color; + } + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + if ((_color >> 24) != 0xFF) // not fully transparent + buf.fillRect(rc, _color); + } + @property override int width() { return 1; } + @property override int height() { return 1; } +} + +class ImageDrawable : Drawable { + protected DrawBufRef _image; + protected bool _tiled; + //private int _instanceCount; + this(ref DrawBufRef image, bool tiled = false, bool ninePatch = false) { + _image = image; + _tiled = tiled; + if (ninePatch) + _image.detectNinePatch(); + //Log.d("Created ImageDrawable, count=", ++_instanceCount); + } + ~this() { + _image.clear(); + //Log.d("Destroyed ImageDrawable, count=", --_instanceCount); + } + @property override int width() { + if (_image.isNull) + return 0; + if (_image.hasNinePatch) + return _image.width - 2; + return _image.width; + } + @property override int height() { + if (_image.isNull) + return 0; + if (_image.hasNinePatch) + return _image.height - 2; + return _image.height; + } + @property override Rect padding() { + if (!_image.isNull && _image.hasNinePatch) + return _image.ninePatch.padding; + return Rect(0,0,0,0); + } + private static void correctFrameBounds(ref int n1, ref int n2, ref int n3, ref int n4) { + if (n1 > n2) { + //assert(n2 - n1 == n4 - n3); + int middledist = (n1 + n2) / 2 - n1; + n1 = n2 = n1 + middledist; + n3 = n4 = n3 + middledist; + } + } + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + if (_image.isNull) + return; + if (_image.hasNinePatch) { + // draw nine patch + const NinePatch * p = _image.ninePatch; + //Log.d("drawing nine patch image with frame ", p.frame, " padding ", p.padding); + int w = width; + int h = height; + Rect dstrect = rc; + Rect srcrect = Rect(1, 1, w + 1, h + 1); + if (true) { //buf.applyClipping(dstrect, srcrect)) { + int x0 = srcrect.left; + int x1 = srcrect.left + p.frame.left; + int x2 = srcrect.right - p.frame.right; + int x3 = srcrect.right; + int y0 = srcrect.top; + int y1 = srcrect.top + p.frame.top; + int y2 = srcrect.bottom - p.frame.bottom; + int y3 = srcrect.bottom; + int dstx0 = rc.left; + int dstx1 = rc.left + p.frame.left; + int dstx2 = rc.right - p.frame.right; + int dstx3 = rc.right; + int dsty0 = rc.top; + int dsty1 = rc.top + p.frame.top; + int dsty2 = rc.bottom - p.frame.bottom; + int dsty3 = rc.bottom; + //Log.d("x bounds: ", x0, ", ", x1, ", ", x2, ", ", x3, " dst ", dstx0, ", ", dstx1, ", ", dstx2, ", ", dstx3); + //Log.d("y bounds: ", y0, ", ", y1, ", ", y2, ", ", y3, " dst ", dsty0, ", ", dsty1, ", ", dsty2, ", ", dsty3); + + correctFrameBounds(x1, x2, dstx1, dstx2); + correctFrameBounds(y1, y2, dsty1, dsty2); + + //correctFrameBounds(x1, x2); + //correctFrameBounds(y1, y2); + //correctFrameBounds(dstx1, dstx2); + //correctFrameBounds(dsty1, dsty2); + if (y0 < y1 && dsty0 < dsty1) { + // top row + if (x0 < x1 && dstx0 < dstx1) + buf.drawFragment(dstx0, dsty0, _image.get, Rect(x0, y0, x1, y1)); // top left + if (x1 < x2 && dstx1 < dstx2) + buf.drawRescaled(Rect(dstx1, dsty0, dstx2, dsty1), _image.get, Rect(x1, y0, x2, y1)); // top center + if (x2 < x3 && dstx2 < dstx3) + buf.drawFragment(dstx2, dsty0, _image.get, Rect(x2, y0, x3, y1)); // top right + } + if (y1 < y2 && dsty1 < dsty2) { + // middle row + if (x0 < x1 && dstx0 < dstx1) + buf.drawRescaled(Rect(dstx0, dsty1, dstx1, dsty2), _image.get, Rect(x0, y1, x1, y2)); // middle center + if (x1 < x2 && dstx1 < dstx2) + buf.drawRescaled(Rect(dstx1, dsty1, dstx2, dsty2), _image.get, Rect(x1, y1, x2, y2)); // center + if (x2 < x3 && dstx2 < dstx3) + buf.drawRescaled(Rect(dstx2, dsty1, dstx3, dsty2), _image.get, Rect(x2, y1, x3, y2)); // middle center + } + if (y2 < y3 && dsty2 < dsty3) { + // bottom row + if (x0 < x1 && dstx0 < dstx1) + buf.drawFragment(dstx0, dsty2, _image.get, Rect(x0, y2, x1, y3)); // bottom left + if (x1 < x2 && dstx1 < dstx2) + buf.drawRescaled(Rect(dstx1, dsty2, dstx2, dsty3), _image.get, Rect(x1, y2, x2, y3)); // bottom center + if (x2 < x3 && dstx2 < dstx3) + buf.drawFragment(dstx2, dsty2, _image.get, Rect(x2, y2, x3, y3)); // bottom right + } + } + } else if (_tiled) { + // tiled + } else { + // rescaled or normal + if (rc.width != _image.width || rc.height != _image.height) + buf.drawRescaled(rc, _image.get, Rect(0, 0, _image.width, _image.height)); + else + buf.drawImage(rc.left, rc.top, _image); + } + } +} + +string attrValue(Element item, string attrname, string attrname2) { + if (attrname in item.tag.attr) + return item.tag.attr[attrname]; + if (attrname2 in item.tag.attr) + return item.tag.attr[attrname2]; + return null; +} + +string attrValue(ref string[string] attr, string attrname, string attrname2) { + if (attrname in attr) + return attr[attrname]; + if (attrname2 in attr) + return attr[attrname2]; + return null; +} + +void extractStateFlag(ref string[string] attr, string attrName, string attrName2, State state, ref uint stateMask, ref uint stateValue) { + string value = attrValue(attr, attrName, attrName2); + if (value !is null) { + if (value.equal("true")) + stateValue |= state; + stateMask |= state; + } +} + +/// converts XML attribute name to State (see http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) +void extractStateFlags(ref string[string] attr, ref uint stateMask, ref uint stateValue) { + extractStateFlag(attr, "state_pressed", "android:state_pressed", State.Pressed, stateMask, stateValue); + extractStateFlag(attr, "state_focused", "android:state_focused", State.Focused, stateMask, stateValue); + extractStateFlag(attr, "state_hovered", "android:state_hovered", State.Hovered, stateMask, stateValue); + extractStateFlag(attr, "state_selected", "android:state_selected", State.Selected, stateMask, stateValue); + extractStateFlag(attr, "state_checkable", "android:state_checkable", State.Checkable, stateMask, stateValue); + extractStateFlag(attr, "state_checked", "android:state_checked", State.Checked, stateMask, stateValue); + extractStateFlag(attr, "state_enabled", "android:state_enabled", State.Enabled, stateMask, stateValue); + extractStateFlag(attr, "state_activated", "android:state_activated", State.Activated, stateMask, stateValue); + extractStateFlag(attr, "state_window_focused", "android:state_window_focused", State.WindowFocused, stateMask, stateValue); +} + +/* +sample: +(prefix android: is optional) + + + + + +*/ + +/// Drawable which is drawn depending on state (see http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) +class StateDrawable : Drawable { + + static struct StateItem { + uint stateMask; + uint stateValue; + DrawableRef drawable; + @property bool matchState(uint state) { + return (stateMask & state) == stateValue; + } + } + protected StateItem[] _stateList; + + void addState(uint stateMask, uint stateValue, string resourceId) { + StateItem item; + item.stateMask = stateMask; + item.stateValue = stateValue; + item.drawable = drawableCache.get(resourceId); + _stateList ~= item; + } + + void addState(uint stateMask, uint stateValue, DrawableRef drawable) { + StateItem item; + item.stateMask = stateMask; + item.stateValue = stateValue; + item.drawable = drawable; + _stateList ~= item; + } + + bool load(Element element) { + foreach(item; element.elements) { + if (item.tag.name.equal("item")) { + string drawableId = attrValue(item, "drawable", "android:drawable"); + if (drawableId !is null) { + uint stateMask, stateValue; + extractStateFlags(item.tag.attr, stateMask, stateValue); + if (drawableId !is null) { + addState(stateMask, stateValue, drawableId); + } + } + } + } + return _stateList.length > 0; + } + + /// load from XML file + bool load(string filename) { + import std.file; + import std.string; + + try { + string s = cast(string)std.file.read(filename); + + // Check for well-formedness + check(s); + + // Make a DOM tree + auto doc = new Document(s); + + return load(doc); + } catch (CheckException e) { + Log.e("Invalid XML file ", filename); + return false; + } catch (Throwable e) { + Log.e("Cannot read drawable resource from file ", filename); + return false; + } + } + + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + foreach(ref item; _stateList) + if (item.matchState(state)) { + item.drawable.drawTo(buf, rc, state, tilex0, tiley0); + return; + } + } + + @property override int width() { + return (_stateList.length > 0) ? _stateList[0].drawable.width : 0; + } + @property override int height() { + return (_stateList.length > 0) ? _stateList[0].drawable.height : 0; + } + @property override Rect padding() { + return (_stateList.length > 0) ? _stateList[0].drawable.padding : Rect(0,0,0,0); + } +} + +alias DrawableRef = Ref!Drawable; + + + + + +/// 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; + } + // clear usage flags for all entries + void checkpoint() { + foreach (item; _map) + item.checkpoint(); + } + // removes entries not used after last call of checkpoint() or cleanup() + void cleanup() { + foreach (item; _map) + item.cleanup(); + } + + this() { + Log.i("Creating ImageCache"); + } + ~this() { + Log.i("Destroying ImageCache"); + foreach (ref item; _map) { + destroy(item); + item = null; + } + _map.clear(); + } +} + +__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; + //private int _instanceCount; + this(string id, string filename, bool tiled) { + _id = id; + _filename = filename; + _tiled = tiled; + _error = filename is null; + //Log.d("Created DrawableCacheItem, count=", ++_instanceCount); + } + ~this() { + _drawable.clear(); + //Log.d("Destroyed DrawableCacheItem, count=", --_instanceCount); + } + /// 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(); + } + /// returns drawable (loads from file if necessary) + @property ref DrawableRef drawable() { + _used = true; + if (!_drawable.isNull || _error) + return _drawable; + if (_filename !is null) { + // reload from file + if (_filename.endsWith(".xml")) { + // XML drawables support + StateDrawable d = new StateDrawable(); + if (!d.load(_filename)) { + destroy(d); + _error = true; + } else { + _drawable = d; + } + } else { + // PNG/JPEG drawables support + 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) + 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(); + } + /// concatenates path with resource id and extension, returns pathname if there is such file, null if file does not exist + private string checkFileName(string path, string id, string extension) { + char[] fn = path.dup; + fn ~= id; + fn ~= extension; + if (exists(fn) && isFile(fn)) + return fn.dup; + return null; + } + string findResource(string id) { + if (id in _idToFileMap) + return _idToFileMap[id]; + foreach(string path; _resourcePaths) { + string fn; + fn = checkFileName(path, id, ".xml"); + if (fn is null) + fn = checkFileName(path, id, ".png"); + if (fn is null) + fn = checkFileName(path, id, ".9.png"); + if (fn is null) + fn = checkFileName(path, id, ".jpg"); + if (fn !is null) { + _idToFileMap[id] = fn; + return fn; + } + } + return null; + } + this() { + Log.i("Creating DrawableCache"); + } + ~this() { + Log.i("Destroying DrawableCache"); + foreach (ref item; _idToDrawableMap) { + destroy(item); + item = null; + } + _idToDrawableMap.clear(); + } +} + diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 6d8bf1ec..7d51c8b9 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -3,7 +3,8 @@ module dlangui.widgets.styles; import dlangui.core.types; import dlangui.graphics.fonts; import dlangui.graphics.drawbuf; -import dlangui.graphics.images; +//import dlangui.graphics.images; +import dlangui.graphics.resources; immutable ubyte ALIGN_UNSPECIFIED = 0; immutable uint COLOR_UNSPECIFIED = 0xFFDEADFF; @@ -20,22 +21,6 @@ immutable int FILL_PARENT = int.max - 1; immutable int WRAP_CONTENT = int.max - 2; immutable int WEIGHT_UNSPECIFIED = -1; -/// widget state flags - bits -enum State : uint { - /// state not specified / normal - Normal = 0, - Pressed = 1, - Focused = 2, - Enabled = 4, - Hovered = 8, // mouse pointer is over control, buttons not pressed - Selected = 16, - Checkable = 32, - Checked = 64, - Activated = 128, - WindowFocused = 256, - Parent = 0x10000, // use parent's state -} - enum Align : ubyte { Unspecified = ALIGN_UNSPECIFIED, Left = 1, @@ -658,11 +643,12 @@ Theme createDefaultTheme() { tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000); tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0); Style tabUpButton = res.createSubstyle("TAB_UP_BUTTON"); - tabUpButton.backgroundImageId("tab_btn_up_normal"); - tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected"); - tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected"); - tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused"); - tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover"); + tabUpButton.backgroundImageId("tab_btn_up"); + //tabUpButton.backgroundImageId("tab_btn_up_normal"); + //tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected"); + //tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected"); + //tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused"); + //tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover"); Style tabHost = res.createSubstyle("TAB_HOST"); tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); tabHost.backgroundColor(0xF0F0F0); diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index ad4f29eb..4a519408 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -4,7 +4,8 @@ public import dlangui.core.types; public import dlangui.core.events; public import dlangui.widgets.styles; public import dlangui.graphics.drawbuf; -public import dlangui.graphics.images; +//public import dlangui.graphics.images; +public import dlangui.graphics.resources; public import dlangui.graphics.fonts; public import dlangui.core.i18n;