mirror of https://github.com/buggins/dlangui.git
commit
29ff80b049
|
@ -858,20 +858,26 @@ class DrawBuf : RefCountedObject {
|
|||
|
||||
alias DrawBufRef = Ref!DrawBuf;
|
||||
|
||||
/// RAII setting/restoring of clip rectangle
|
||||
/// RAII setting/restoring of a DrawBuf clip rectangle
|
||||
struct ClipRectSaver {
|
||||
private DrawBuf _buf;
|
||||
private Rect _oldClipRect;
|
||||
private uint _oldAlpha;
|
||||
/// apply (intersect) new clip rectangle and alpha to draw buf; restore
|
||||
this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0) {
|
||||
|
||||
/// apply (intersect) new clip rectangle and alpha to draw buf
|
||||
/// set `intersect` parameter to `false`, if you want to draw something outside of the widget
|
||||
this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0, bool intersect = true) {
|
||||
_buf = buf;
|
||||
_oldClipRect = buf.clipRect;
|
||||
_oldAlpha = buf.alpha;
|
||||
buf.intersectClipRect(newClipRect);
|
||||
if (intersect)
|
||||
buf.intersectClipRect(newClipRect);
|
||||
else
|
||||
buf.clipRect = newClipRect;
|
||||
if (newAlpha)
|
||||
buf.addAlpha(newAlpha);
|
||||
}
|
||||
/// restore previous clip rectangle
|
||||
~this() {
|
||||
_buf.clipRect = _oldClipRect;
|
||||
_buf.alpha = _oldAlpha;
|
||||
|
@ -1224,7 +1230,6 @@ class ColorDrawBufBase : DrawBuf {
|
|||
row[x] = blendARGB(row[x], color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GrayDrawBuf : DrawBuf {
|
||||
|
@ -1469,6 +1474,7 @@ class GrayDrawBuf : DrawBuf {
|
|||
|
||||
class ColorDrawBuf : ColorDrawBufBase {
|
||||
uint[] _buf;
|
||||
|
||||
/// create ARGB8888 draw buf of specified width and height
|
||||
this(int width, int height) {
|
||||
resize(width, height);
|
||||
|
@ -1525,11 +1531,13 @@ class ColorDrawBuf : ColorDrawBufBase {
|
|||
pixel ^= 0xFF000000;
|
||||
}
|
||||
}
|
||||
|
||||
override uint * scanLine(int y) {
|
||||
if (y >= 0 && y < _dy)
|
||||
return _buf.ptr + _dx * y;
|
||||
return null;
|
||||
}
|
||||
|
||||
override void resize(int width, int height) {
|
||||
if (_dx == width && _dy == height)
|
||||
return;
|
||||
|
@ -1538,6 +1546,7 @@ class ColorDrawBuf : ColorDrawBufBase {
|
|||
_buf.length = _dx * _dy;
|
||||
resetClipping();
|
||||
}
|
||||
|
||||
override void fill(uint color) {
|
||||
if (hasClipping) {
|
||||
fillRect(_clipRect, color);
|
||||
|
@ -1548,6 +1557,7 @@ class ColorDrawBuf : ColorDrawBufBase {
|
|||
foreach(i; 0 .. len)
|
||||
p[i] = color;
|
||||
}
|
||||
|
||||
override DrawBuf transformColors(ref ColorTransform transform) {
|
||||
if (transform.empty)
|
||||
return this;
|
||||
|
@ -1572,6 +1582,71 @@ class ColorDrawBuf : ColorDrawBufBase {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Apply Gaussian blur on the image
|
||||
void blur(uint blurSize) {
|
||||
if (blurSize == 0)
|
||||
return; // trivial case
|
||||
|
||||
// utility functions to get and set color
|
||||
float[4] get(uint[] buf, uint x, uint y) {
|
||||
uint c = buf[x + y * _dx];
|
||||
float a = 255 - (c >> 24);
|
||||
float r = (c >> 16) & 0xFF;
|
||||
float g = (c >> 8) & 0xFF;
|
||||
float b = (c >> 0) & 0xFF;
|
||||
return [r, g, b, a];
|
||||
}
|
||||
void set(uint[] buf, uint x, uint y, float[4] c) {
|
||||
buf[x + y * _dx] = makeRGBA(c[0], c[1], c[2], 255 - c[3]);
|
||||
}
|
||||
|
||||
|
||||
import std.algorithm : max, min;
|
||||
import std.math;
|
||||
|
||||
// Gaussian function
|
||||
float weight(in float x, in float sigma) pure nothrow {
|
||||
enum inv_sqrt_2pi = 1 / sqrt(2 * PI);
|
||||
return exp(- x ^^ 2 / (2 * sigma ^^ 2)) * inv_sqrt_2pi / sigma;
|
||||
}
|
||||
|
||||
void blurOneDimension(uint[] bufIn, uint[] bufOut, uint blurSize, bool horizontally) {
|
||||
|
||||
float sigma = blurSize > 2 ? blurSize / 3.0 : blurSize / 2.0;
|
||||
|
||||
foreach (x; 0 .. _dx) {
|
||||
foreach (y; 0 .. _dy) {
|
||||
float[4] c;
|
||||
c[] = 0;
|
||||
|
||||
float sum = 0;
|
||||
foreach (int i; 1 .. blurSize + 1) {
|
||||
float[4] c1 = get(bufIn,
|
||||
horizontally ? min(x + i, _dx - 1) : x,
|
||||
horizontally ? y : min(y + i, _dy - 1)
|
||||
);
|
||||
float[4] c2 = get(bufIn,
|
||||
horizontally ? max(x - i, 0) : x,
|
||||
horizontally ? y : max(y - i, 0)
|
||||
);
|
||||
float w = weight(i, sigma);
|
||||
c[] += (c1[] + c2[]) * w;
|
||||
sum += 2 * w;
|
||||
}
|
||||
c[] += get(bufIn, x, y)[] * (1 - sum);
|
||||
set(bufOut, x, y, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
// intermediate buffer for image
|
||||
uint[] tmpbuf;
|
||||
tmpbuf.length = _buf.length;
|
||||
// do horizontal blur
|
||||
blurOneDimension(_buf, tmpbuf, blurSize, true);
|
||||
// then do vertical blur
|
||||
blurOneDimension(tmpbuf, _buf, blurSize, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -408,6 +408,71 @@ class BorderDrawable : Drawable {
|
|||
}
|
||||
deprecated alias FrameDrawable = BorderDrawable;
|
||||
|
||||
/// box shadows, can be blurred
|
||||
class BoxShadowDrawable : Drawable {
|
||||
protected int _offsetX;
|
||||
protected int _offsetY;
|
||||
protected int _blurSize;
|
||||
protected uint _color;
|
||||
protected Ref!ColorDrawBuf texture;
|
||||
|
||||
this(int offsetX, int offsetY, uint blurSize = 0, uint color = 0x0) {
|
||||
_offsetX = offsetX;
|
||||
_offsetY = offsetY;
|
||||
_blurSize = blurSize;
|
||||
_color = color;
|
||||
// now create a texture which will contain the shadow
|
||||
uint size = 4 * blurSize + 1;
|
||||
texture = new ColorDrawBuf(size, size); // TODO: get from/put to cache
|
||||
// clear
|
||||
texture.fill(color | 0xFF000000);
|
||||
// draw a square in center of the texture
|
||||
texture.fillRect(Rect(blurSize, blurSize, size - blurSize, size - blurSize), color);
|
||||
// blur the square
|
||||
texture.blur(blurSize);
|
||||
}
|
||||
|
||||
override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) {
|
||||
// this is a size of blurred part
|
||||
uint b = _blurSize + _blurSize / 2 + 1;
|
||||
// move and expand the shadow
|
||||
rc.left += _offsetX - b;
|
||||
rc.top += _offsetY - b;
|
||||
rc.right += _offsetX + b;
|
||||
rc.bottom += _offsetY + b;
|
||||
|
||||
// apply new clipping to the DrawBuf to draw outside of the widget
|
||||
auto saver = ClipRectSaver(buf, rc, 0, false);
|
||||
|
||||
if (_blurSize > 0) {
|
||||
// Manual nine-patch
|
||||
uint w = texture.width;
|
||||
uint h = texture.height;
|
||||
|
||||
buf.drawFragment(rc.left, rc.top, texture, Rect(0, 0, b, b)); // top left
|
||||
buf.drawRescaled(Rect(rc.left + b, rc.top, rc.right - b, rc.top + b), texture, Rect(b, 0, w - b, b)); // top center
|
||||
buf.drawFragment(rc.right - b, rc.top, texture, Rect(w - b, 0, w, b)); // top right
|
||||
|
||||
buf.drawRescaled(Rect(rc.left, rc.top + b, rc.left + b, rc.bottom - b), texture, Rect(0, b, b, h - b)); // middle left
|
||||
buf.drawRescaled(Rect(rc.left + b, rc.top + b, rc.right - b, rc.bottom - b), texture, Rect(b, b, w - b, h - b)); // middle center
|
||||
buf.drawRescaled(Rect(rc.right - b, rc.top + b, rc.right, rc.bottom - b), texture, Rect(w - b, b, w, h - b)); // middle right
|
||||
|
||||
buf.drawFragment(rc.left, rc.bottom - b, texture, Rect(0, h - b, b, h)); // bottom left
|
||||
buf.drawRescaled(Rect(rc.left + b, rc.bottom - b, rc.right - b, rc.bottom), texture, Rect(b, h - b, w - b, h)); // bottom center
|
||||
buf.drawFragment(rc.right - b, rc.bottom - b, texture, Rect(w - b, h - b, w, h)); // bottom right
|
||||
|
||||
// debug
|
||||
//~ buf.drawFragment(rc.left, rc.top, texture, Rect(0, 0, w, h));
|
||||
} else {
|
||||
buf.fillRect(rc, _color);
|
||||
}
|
||||
}
|
||||
|
||||
@property override int width() { return 1; }
|
||||
@property override int height() { return 1; }
|
||||
}
|
||||
|
||||
|
||||
enum DimensionUnits {
|
||||
pixels,
|
||||
points,
|
||||
|
@ -491,32 +556,42 @@ static if (BACKEND_CONSOLE) {
|
|||
/// decode solid color / gradient / border drawable from string like #AARRGGBB, e.g. #5599AA
|
||||
///
|
||||
/// SolidFillDrawable: #AARRGGBB - e.g. #8090A0 or #80ffffff
|
||||
/// GradientDrawable: #linear,Ndeg,#firstColor,#secondColor
|
||||
/// BorderDrawable: #borderColor,borderWidth[,#middleColor]
|
||||
/// or #borderColor,leftBorderWidth,topBorderWidth,rightBorderWidth,bottomBorderWidth[,#middleColor]
|
||||
/// e.g. #000000,2,#C0FFFFFF - black border of width 2 with 75% transparent white middle
|
||||
/// e.g. #0000FF,2,3,4,5,#FFFFFF - blue border with left,top,right,bottom borders of width 2,3,4,5 and white inner area
|
||||
/// GradientDrawable: #linear,Ndeg,firstColor,secondColor
|
||||
/// BorderDrawable: #border,borderColor,borderWidth[,middleColor]
|
||||
/// or #border,borderColor,leftBorderWidth,topBorderWidth,rightBorderWidth,bottomBorderWidth[,middleColor]
|
||||
/// e.g. #border,#000000,2,#C0FFFFFF - black border of width 2 with 75% transparent white middle
|
||||
/// e.g. #border,#0000FF,2,3,4,5,#FFFFFF - blue border with left,top,right,bottom borders of width 2,3,4,5 and white inner area
|
||||
static Drawable createColorDrawable(string s) {
|
||||
Log.d("creating color drawable ", s);
|
||||
|
||||
enum DrawableType { SolidColor, LinearGradient, Border }
|
||||
enum DrawableType { SolidColor, LinearGradient, Border, BoxShadow }
|
||||
auto type = DrawableType.SolidColor;
|
||||
|
||||
string[] items = s.split(',');
|
||||
uint[] values;
|
||||
foreach (i, item; items) {
|
||||
if (item == "#linear")
|
||||
int[] ivalues;
|
||||
if (items.length != 0) {
|
||||
if (items[0] == "#linear")
|
||||
type = DrawableType.LinearGradient;
|
||||
else if (item.startsWith("#"))
|
||||
values ~= decodeHexColor(item);
|
||||
else if (item.endsWith("deg"))
|
||||
values ~= decodeAngle(item);
|
||||
else {
|
||||
values ~= decodeDimension(item);
|
||||
else if (items[0] == "#border")
|
||||
type = DrawableType.Border;
|
||||
else if (items[0] == "#box-shadow")
|
||||
type = DrawableType.BoxShadow;
|
||||
else if (items[0].startsWith("#"))
|
||||
values ~= decodeHexColor(items[0]);
|
||||
|
||||
foreach (i, item; items[1 .. $]) {
|
||||
if (item.startsWith("#"))
|
||||
values ~= decodeHexColor(item);
|
||||
else if (item.endsWith("deg"))
|
||||
values ~= decodeAngle(item);
|
||||
else if (type == DrawableType.BoxShadow) // offsets may be negative
|
||||
ivalues ~= item.startsWith("-") ? -decodeDimension(item) : decodeDimension(item);
|
||||
else
|
||||
values ~= decodeDimension(item);
|
||||
if (i >= 6)
|
||||
break;
|
||||
}
|
||||
if (i >= 6)
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == DrawableType.SolidColor && values.length == 1) // only color #AARRGGBB
|
||||
|
@ -532,6 +607,13 @@ static Drawable createColorDrawable(string s) {
|
|||
return new BorderDrawable(values[0], Rect(values[1], values[2], values[3], values[4]));
|
||||
else if (values.length == 6) // border color, border widths for left,top,right,bottom, inner area color - #AARRGGBB,NNleft,NNtop,NNright,NNbottom,#AARRGGBB
|
||||
return new BorderDrawable(values[0], Rect(values[1], values[2], values[3], values[4]), values[5]);
|
||||
} else if (type == DrawableType.BoxShadow) {
|
||||
if (ivalues.length == 2 && values.length == 0) // shadow X and Y offsets
|
||||
return new BoxShadowDrawable(ivalues[0], ivalues[1]);
|
||||
else if (ivalues.length == 3 && values.length == 0) // shadow offsets and blur size
|
||||
return new BoxShadowDrawable(ivalues[0], ivalues[1], ivalues[2]);
|
||||
else if (ivalues.length == 3 && values.length == 1) // shadow offsets, blur size and color
|
||||
return new BoxShadowDrawable(ivalues[0], ivalues[1], ivalues[2], values[0]);
|
||||
}
|
||||
Log.e("Invalid drawable string format: ", s);
|
||||
return new EmptyDrawable(); // invalid format - just return empty drawable
|
||||
|
@ -1133,19 +1215,22 @@ class StateDrawable : Drawable {
|
|||
/// Drawable which allows to combine together background image, gradient, borders, box shadows, etc.
|
||||
class CombinedDrawable : Drawable {
|
||||
|
||||
DrawableRef boxShadow;
|
||||
DrawableRef background;
|
||||
DrawableRef border;
|
||||
|
||||
this(uint backgroundColor, string backgroundImageId, string borderDescription) {
|
||||
this(uint backgroundColor, string backgroundImageId, string borderDescription, string boxShadowDescription) {
|
||||
boxShadow = boxShadowDescription !is null ? drawableCache.get("#box-shadow," ~ boxShadowDescription) : new EmptyDrawable;
|
||||
background =
|
||||
(backgroundImageId !is null) ? drawableCache.get(backgroundImageId) :
|
||||
(!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : null;
|
||||
if (background is null)
|
||||
background = new EmptyDrawable;
|
||||
border = borderDescription !is null ? drawableCache.get(borderDescription) : new EmptyDrawable;
|
||||
border = borderDescription !is null ? drawableCache.get("#border," ~ borderDescription) : new EmptyDrawable;
|
||||
}
|
||||
|
||||
override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) {
|
||||
boxShadow.drawTo(buf, rc, state, tilex0, tiley0);
|
||||
// make background image smaller to fit borders
|
||||
Rect backrc = rc;
|
||||
backrc.left += border.padding.left;
|
||||
|
|
|
@ -324,6 +324,7 @@ protected:
|
|||
uint _alpha;
|
||||
string _fontFace;
|
||||
string _backgroundImageId;
|
||||
string _boxShadow;
|
||||
string _border;
|
||||
Rect _padding;
|
||||
Rect _margins;
|
||||
|
@ -413,8 +414,10 @@ public:
|
|||
return (cast(Style)this)._backgroundDrawable;
|
||||
string image = backgroundImageId;
|
||||
uint color = backgroundColor;
|
||||
if (border !is null) {
|
||||
(cast(Style)this)._backgroundDrawable = new CombinedDrawable(color, image, border);
|
||||
string borders = border;
|
||||
string shadows = boxShadow;
|
||||
if (borders !is null || shadows !is null) {
|
||||
(cast(Style)this)._backgroundDrawable = new CombinedDrawable(color, image, borders, shadows);
|
||||
} else if (image !is null) {
|
||||
(cast(Style)this)._backgroundDrawable = drawableCache.get(image);
|
||||
} else {
|
||||
|
@ -530,6 +533,15 @@ public:
|
|||
return parentStyle.fontSize;
|
||||
}
|
||||
|
||||
/// box shadow
|
||||
@property string boxShadow() const {
|
||||
if (_boxShadow !is null)
|
||||
return _boxShadow;
|
||||
else {
|
||||
return parentStyle.boxShadow;
|
||||
}
|
||||
}
|
||||
|
||||
/// border
|
||||
@property string border() const {
|
||||
if (_border !is null)
|
||||
|
@ -791,6 +803,12 @@ public:
|
|||
return this;
|
||||
}
|
||||
|
||||
@property Style boxShadow(string s) {
|
||||
_boxShadow = s;
|
||||
_backgroundDrawable.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@property Style border(string s) {
|
||||
_border = s;
|
||||
_backgroundDrawable.clear();
|
||||
|
@ -908,6 +926,7 @@ public:
|
|||
res._alpha = _alpha;
|
||||
res._fontFace = _fontFace;
|
||||
res._backgroundImageId = _backgroundImageId;
|
||||
res._boxShadow = _boxShadow;
|
||||
res._border = _border;
|
||||
res._padding = _padding;
|
||||
res._margins = _margins;
|
||||
|
@ -1038,6 +1057,10 @@ class Theme : Style {
|
|||
@property override string backgroundImageId() const {
|
||||
return _backgroundImageId;
|
||||
}
|
||||
/// box shadow
|
||||
@property override string boxShadow() const {
|
||||
return _boxShadow;
|
||||
}
|
||||
/// border
|
||||
@property override string border() const {
|
||||
return _border;
|
||||
|
@ -1527,6 +1550,11 @@ string sanitizeBorderProperty(string s) pure {
|
|||
return cast(string)res;
|
||||
}
|
||||
|
||||
/// remove superfluous space characters from a box shadow property
|
||||
string sanitizeBoxShadowProperty(string s) pure {
|
||||
return sanitizeBorderProperty(s);
|
||||
}
|
||||
|
||||
/// load style attributes from XML element
|
||||
bool loadStyleAttributes(Style style, Element elem, bool allowStates) {
|
||||
//Log.d("Theme: loadStyleAttributes ", style.id, " ", elem.tag.attr);
|
||||
|
@ -1542,6 +1570,8 @@ bool loadStyleAttributes(Style style, Element elem, bool allowStates) {
|
|||
style.padding = decodeRect(elem.tag.attr["padding"]);
|
||||
if ("border" in elem.tag.attr)
|
||||
style.border = sanitizeBorderProperty(elem.tag.attr["border"]);
|
||||
if ("boxShadow" in elem.tag.attr)
|
||||
style.boxShadow = sanitizeBoxShadowProperty(elem.tag.attr["boxShadow"]);
|
||||
if ("align" in elem.tag.attr)
|
||||
style.alignment = decodeAlignment(elem.tag.attr["align"]);
|
||||
if ("minWidth" in elem.tag.attr)
|
||||
|
|
Loading…
Reference in New Issue