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;