mirror of https://github.com/buggins/dlangui.git
refactoring
This commit is contained in:
parent
f3c0197837
commit
9e562548ba
|
@ -312,6 +312,7 @@
|
||||||
<File path="src\dlangui\graphics\gldrawbuf.d" />
|
<File path="src\dlangui\graphics\gldrawbuf.d" />
|
||||||
<File path="src\dlangui\graphics\glsupport.d" />
|
<File path="src\dlangui\graphics\glsupport.d" />
|
||||||
<File path="src\dlangui\graphics\images.d" />
|
<File path="src\dlangui\graphics\images.d" />
|
||||||
|
<File path="src\dlangui\graphics\resources.d" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder name="platforms">
|
<Folder name="platforms">
|
||||||
<Folder name="common">
|
<Folder name="common">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:constantSize="true"
|
android:constantSize="true"
|
||||||
android:dither="false"
|
android:dither="false"
|
||||||
android:variablePadding="false"
|
android:variablePadding="false" >
|
||||||
<item
|
<item
|
||||||
android:drawable="tab_btn_up_hover"
|
android:drawable="tab_btn_up_hover"
|
||||||
android:state_hovered="true" />
|
android:state_hovered="true" />
|
||||||
|
|
|
@ -193,3 +193,20 @@ wstring fromWStringz(const(wchar[]) s) {
|
||||||
return cast(wstring)(s[0..i].dup);
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:constantSize=["true" | "false"]
|
|
||||||
android:dither=["true" | "false"]
|
|
||||||
android:variablePadding=["true" | "false"] >
|
|
||||||
<item
|
|
||||||
android:drawable="@[package:]drawable/drawable_resource"
|
|
||||||
android:state_pressed=["true" | "false"]
|
|
||||||
android:state_focused=["true" | "false"]
|
|
||||||
android:state_hovered=["true" | "false"]
|
|
||||||
android:state_selected=["true" | "false"]
|
|
||||||
android:state_checkable=["true" | "false"]
|
|
||||||
android:state_checked=["true" | "false"]
|
|
||||||
android:state_enabled=["true" | "false"]
|
|
||||||
android:state_activated=["true" | "false"]
|
|
||||||
android:state_window_focused=["true" | "false"] />
|
|
||||||
</selector>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
|
|
|
@ -4,231 +4,8 @@ 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.algorithm;
|
|
||||||
import libpng.png;
|
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
|
/// 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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:constantSize=["true" | "false"]
|
||||||
|
android:dither=["true" | "false"]
|
||||||
|
android:variablePadding=["true" | "false"] >
|
||||||
|
<item
|
||||||
|
android:drawable="@[package:]drawable/drawable_resource"
|
||||||
|
android:state_pressed=["true" | "false"]
|
||||||
|
android:state_focused=["true" | "false"]
|
||||||
|
android:state_hovered=["true" | "false"]
|
||||||
|
android:state_selected=["true" | "false"]
|
||||||
|
android:state_checkable=["true" | "false"]
|
||||||
|
android:state_checked=["true" | "false"]
|
||||||
|
android:state_enabled=["true" | "false"]
|
||||||
|
android:state_activated=["true" | "false"]
|
||||||
|
android:state_window_focused=["true" | "false"] />
|
||||||
|
</selector>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@ module dlangui.widgets.styles;
|
||||||
import dlangui.core.types;
|
import dlangui.core.types;
|
||||||
import dlangui.graphics.fonts;
|
import dlangui.graphics.fonts;
|
||||||
import dlangui.graphics.drawbuf;
|
import dlangui.graphics.drawbuf;
|
||||||
import dlangui.graphics.images;
|
//import dlangui.graphics.images;
|
||||||
|
import dlangui.graphics.resources;
|
||||||
|
|
||||||
immutable ubyte ALIGN_UNSPECIFIED = 0;
|
immutable ubyte ALIGN_UNSPECIFIED = 0;
|
||||||
immutable uint COLOR_UNSPECIFIED = 0xFFDEADFF;
|
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 WRAP_CONTENT = int.max - 2;
|
||||||
immutable int WEIGHT_UNSPECIFIED = -1;
|
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 {
|
enum Align : ubyte {
|
||||||
Unspecified = ALIGN_UNSPECIFIED,
|
Unspecified = ALIGN_UNSPECIFIED,
|
||||||
Left = 1,
|
Left = 1,
|
||||||
|
@ -658,11 +643,12 @@ Theme createDefaultTheme() {
|
||||||
tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000);
|
tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000);
|
||||||
tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0);
|
tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0);
|
||||||
Style tabUpButton = res.createSubstyle("TAB_UP_BUTTON");
|
Style tabUpButton = res.createSubstyle("TAB_UP_BUTTON");
|
||||||
tabUpButton.backgroundImageId("tab_btn_up_normal");
|
tabUpButton.backgroundImageId("tab_btn_up");
|
||||||
tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected");
|
//tabUpButton.backgroundImageId("tab_btn_up_normal");
|
||||||
tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected");
|
//tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected");
|
||||||
tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused");
|
//tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected");
|
||||||
tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover");
|
//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");
|
Style tabHost = res.createSubstyle("TAB_HOST");
|
||||||
tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
|
tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
|
||||||
tabHost.backgroundColor(0xF0F0F0);
|
tabHost.backgroundColor(0xF0F0F0);
|
||||||
|
|
|
@ -4,7 +4,8 @@ public import dlangui.core.types;
|
||||||
public import dlangui.core.events;
|
public import dlangui.core.events;
|
||||||
public import dlangui.widgets.styles;
|
public import dlangui.widgets.styles;
|
||||||
public import dlangui.graphics.drawbuf;
|
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.graphics.fonts;
|
||||||
public import dlangui.core.i18n;
|
public import dlangui.core.i18n;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue