mirror of https://github.com/buggins/dlangui.git
support loading of theme from .xml file
This commit is contained in:
parent
b69d55f3eb
commit
1f9e1eba27
|
@ -96,6 +96,12 @@ extern (C) int UIAppMain(string[] args) {
|
|||
// select translation file - for english language
|
||||
i18n.load("en.ini"); //"ru.ini", "en.ini"
|
||||
|
||||
Theme theme = loadTheme("theme_default");
|
||||
if (theme) {
|
||||
Log.d("Applying loaded theme ", theme.id);
|
||||
currentTheme = theme;
|
||||
}
|
||||
|
||||
// create window
|
||||
Window window = Platform.instance.createWindow("My Window", null);
|
||||
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<theme id="theme_default" >
|
||||
<style id="BUTTON"
|
||||
backgroundImageId="btn_default_small"
|
||||
align="Center"
|
||||
margins="5,5,5,5"
|
||||
/>
|
||||
<style id="BUTTON_TRANSPARENT"
|
||||
backgroundImageId="btn_default_small_transparent"
|
||||
align="Center"
|
||||
/>
|
||||
<style id="BUTTON_LABEL"
|
||||
layoutWidth="FILL_PARENT"
|
||||
align="Left|VCenter"
|
||||
/>
|
||||
<style id="BUTTON_ICON"
|
||||
align="Center"
|
||||
/>
|
||||
<style id="TEXT"
|
||||
margins="2,2,2,2"
|
||||
padding="1,1,1,1"
|
||||
/>
|
||||
<style id="HSPACER"
|
||||
layoutWidth="FILL_PARENT"
|
||||
layoutWeight="100"
|
||||
minWidth="5"
|
||||
/>
|
||||
<style id="VSPACER"
|
||||
layoutHeight="FILL_PARENT"
|
||||
layoutWeight="100"
|
||||
minHeight="5"
|
||||
/>
|
||||
<style id="BUTTON_NOMARGINS"
|
||||
backgroundImageId="btn_default_small"
|
||||
align="Center"
|
||||
/>
|
||||
<drawable id="scrollbar_button_up" value="scrollbar_btn_up"/>
|
||||
<drawable id="scrollbar_button_down" value="scrollbar_btn_down"/>
|
||||
<srawable id="scrollbar_button_left" value="scrollbar_btn_left"/>
|
||||
<drawable id="scrollbar_button_right" value="scrollbar_btn_right"/>
|
||||
<drawable id="scrollbar_indicator_vertical" value="scrollbar_indicator_vertical"/>
|
||||
<drawable id="scrollbar_indicator_horizontal" value="scrollbar_indicator_horizontal"/>
|
||||
<style id="SCROLLBAR"
|
||||
backgroundColor="#C0808080"
|
||||
align="Center"
|
||||
/>
|
||||
<style id="SCROLLBAR_BUTTON" parent="BUTTON"
|
||||
/>
|
||||
<style id="SLIDER"
|
||||
/>
|
||||
<style id="PAGE_SCROLL"
|
||||
backgroundColor="#FFFFFFFF"
|
||||
>
|
||||
<state state_pressed="true" backgroundColor="#C0404080"/>
|
||||
<state state_hovered="true" backgroundColor="#F0404080"/>
|
||||
</style>
|
||||
<style id="TAB_UP"
|
||||
backgroundImageId="tab_up_background"
|
||||
layoutWidth="FILL_PARENT"
|
||||
>
|
||||
<state state_selected="true" backgroundImageId="tab_up_backgrond_selected"/>
|
||||
</style>
|
||||
<style id="TAB_UP_BUTTON_TEXT"
|
||||
textColor="#000000"
|
||||
backgroundImageId="tab_up_background"
|
||||
fontSize="12"
|
||||
align="Center"
|
||||
>
|
||||
<state state_selected="true" state_focused="true" textColor="#000000"/>
|
||||
<state state_selected="true" textColor="#000000"/>
|
||||
<state state_focused="true" textColor="#000000"/>
|
||||
<state state_hovered="true" textColor="#FFE0E0"/>
|
||||
</style>
|
||||
<style id="TAB_UP_BUTTON"
|
||||
backgroundImageId="tab_btn_up"
|
||||
/>
|
||||
<style id="TAB_HOST"
|
||||
layoutWidth="FILL_PARENT"
|
||||
layoutHeight="FILL_PARENT"
|
||||
backgroundColor="#F0F0F0"
|
||||
/>
|
||||
<style id="TAB_WIDGET"
|
||||
padding="3,3,3,3"
|
||||
backgroundColor="#EEEEEE"
|
||||
/>
|
||||
<style id="MAIN_MENU"
|
||||
layoutWidth="FILL_PARENT"
|
||||
backgroundColor="#EFEFF2"
|
||||
/>
|
||||
<style id="MAIN_MENU_ITEM"
|
||||
padding="4,2,4,2"
|
||||
backgroundImageId="main_menu_item_background"
|
||||
textFlags="Parent"
|
||||
/>
|
||||
<style id="MENU_ITEM"
|
||||
padding="4,2,4,2"
|
||||
>
|
||||
<state state_focused="true" backgroundColor="#40C0C000"/>
|
||||
<state state_pressed="true" backgroundColor="#4080C000"/>
|
||||
<state state_selected="true" backgroundColor="#00F8F9Fa"/>
|
||||
<state state_hovered="true" backgroundColor="#C0FFFF00"/>
|
||||
</style>
|
||||
<style id="MENU_ICON"
|
||||
margins="2,2,2,2"
|
||||
align="Left|VCenter"
|
||||
>
|
||||
<state state_enabled="false" alpha="160"/>
|
||||
</style>
|
||||
<style id="MENU_LABEL"
|
||||
margins="4,2,4,2"
|
||||
align="Left|VCenter"
|
||||
textFlags="UnderlineHotKeys"
|
||||
>
|
||||
<state state_enabled="false" textColor="#80404040"/>
|
||||
</style>
|
||||
<style id="MAIN_MENU_LABEL"
|
||||
margins="4,2,4,2"
|
||||
align="Left|VCenter"
|
||||
textFlags="Parent"
|
||||
>
|
||||
<state state_enabled="false" textColor="#80404040"/>
|
||||
</style>
|
||||
<style id="MENU_ACCEL"
|
||||
margins="4,2,4,2"
|
||||
align="Left|VCenter"
|
||||
>
|
||||
<state state_enabled="false" textColor="#80404040"/>
|
||||
</style>
|
||||
<style id="TRANSPARENT_BUTTON_BACKGROUND"
|
||||
backgroundImageId="transparent_button_background"
|
||||
padding="4,2,4,2"
|
||||
/>
|
||||
<style id="POPUP_MENU"
|
||||
backgroundImageId="popup_menu_background_normal"
|
||||
/>
|
||||
<style id="LIST_ITEM"
|
||||
backgroundImageId="list_item_background"
|
||||
/>
|
||||
<style id="EDIT_LINE"
|
||||
backgroundImageId="editbox_background"
|
||||
padding="5,6,5,6"
|
||||
margins="2,2,2,2"
|
||||
minWidth="40"
|
||||
fontFace="Arial"
|
||||
fontFamily="SansSerif"
|
||||
fontSize="16"
|
||||
/>
|
||||
<style id="EDIT_BOX"
|
||||
backgroundImageId="editbox_background"
|
||||
padding="5,6,5,6"
|
||||
margins="2,2,2,2"
|
||||
minWidth="100"
|
||||
minHeight="60"
|
||||
layoutWidth="FILL_PARENT"
|
||||
layoutHeight="FILL_PARENT"
|
||||
fontFace="Courier New"
|
||||
fontFamily="MonoSpace"
|
||||
fontSize="16"
|
||||
/>
|
||||
|
||||
</theme>
|
||||
|
|
@ -299,18 +299,18 @@ class ImageDrawable : Drawable {
|
|||
}
|
||||
}
|
||||
|
||||
string attrValue(Element item, string attrname, string attrname2) {
|
||||
string attrValue(Element item, string attrname, string attrname2 = null) {
|
||||
if (attrname in item.tag.attr)
|
||||
return item.tag.attr[attrname];
|
||||
if (attrname2 in item.tag.attr)
|
||||
if (attrname2 && attrname2 in item.tag.attr)
|
||||
return item.tag.attr[attrname2];
|
||||
return null;
|
||||
}
|
||||
|
||||
string attrValue(ref string[string] attr, string attrname, string attrname2) {
|
||||
string attrValue(ref string[string] attr, string attrname, string attrname2 = null) {
|
||||
if (attrname in attr)
|
||||
return attr[attrname];
|
||||
if (attrname2 in attr)
|
||||
if (attrname2 && attrname2 in attr)
|
||||
return attr[attrname2];
|
||||
return null;
|
||||
}
|
||||
|
@ -863,7 +863,8 @@ class DrawableCache {
|
|||
return fn.dup;
|
||||
return null;
|
||||
}
|
||||
string findResource(string id) {
|
||||
/// get resource file full pathname by resource id, null if not found
|
||||
string findResource(string id) {
|
||||
if (id.startsWith("#"))
|
||||
return id; // it's not a file name, just a color #AARRGGBB
|
||||
if (id in _idToFileMap)
|
||||
|
|
|
@ -22,6 +22,10 @@ Authors: Vadim Lopatin, coolreader.org@gmail.com
|
|||
*/
|
||||
module dlangui.widgets.styles;
|
||||
|
||||
private import std.xml;
|
||||
private import std.string;
|
||||
private import std.algorithm;
|
||||
|
||||
import dlangui.core.types;
|
||||
import dlangui.graphics.fonts;
|
||||
import dlangui.graphics.drawbuf;
|
||||
|
@ -165,6 +169,10 @@ class Style {
|
|||
}
|
||||
|
||||
@property string id() const { return _id; }
|
||||
@property Style id(string id) {
|
||||
this._id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// access to parent style for this style
|
||||
@property const(Style) parentStyle() const {
|
||||
|
@ -683,8 +691,10 @@ class Theme : Style {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// create new named style
|
||||
/// create new named style or get existing
|
||||
override Style createSubstyle(string id) {
|
||||
if (id !is null && id in _byId)
|
||||
return _byId[id]; // already exists
|
||||
Style style = new Style(this, id);
|
||||
if (id !is null)
|
||||
_byId[id] = style;
|
||||
|
@ -824,6 +834,267 @@ Theme createDefaultTheme() {
|
|||
return res;
|
||||
}
|
||||
|
||||
/// decode comma delimited dimension list or single value - and put to Rect
|
||||
Rect decodeRect(string s) {
|
||||
uint[6] values;
|
||||
int valueCount = 0;
|
||||
int start = 0;
|
||||
for (int i = 0; i <= s.length; i++) {
|
||||
if (i == s.length || s[i] == ',') {
|
||||
if (i > start) {
|
||||
string item = s[start .. i];
|
||||
values[valueCount++] = decodeDimension(item);
|
||||
if (valueCount >= 6)
|
||||
break;
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
if (valueCount == 1) // same value for all dimensions
|
||||
return Rect(values[0], values[0], values[0], values[0]);
|
||||
else if (valueCount == 2) // one value of horizontal, and one for vertical
|
||||
return Rect(values[0], values[1], values[0], values[1]);
|
||||
else if (valueCount == 4) // separate left, top, right, bottom
|
||||
return Rect(values[0], values[1], values[2], values[3]);
|
||||
Log.e("Invalid rect attribute value ", s);
|
||||
return Rect(0,0,0,0);
|
||||
}
|
||||
|
||||
/// parses string like "Left|VCenter" to bit set of Align flags
|
||||
ubyte decodeAlignment(string s) {
|
||||
ubyte res = 0;
|
||||
int start = 0;
|
||||
for (int i = 0; i <= s.length; i++) {
|
||||
if (i == s.length || s[i] == '|') {
|
||||
if (i > start) {
|
||||
string item = s[start .. i];
|
||||
if (item.equal("Left"))
|
||||
res |= Align.Left;
|
||||
else if (item.equal("Right"))
|
||||
res |= Align.Right;
|
||||
else if (item.equal("Top"))
|
||||
res |= Align.Top;
|
||||
else if (item.equal("Bottom"))
|
||||
res |= Align.Bottom;
|
||||
else if (item.equal("HCenter"))
|
||||
res |= Align.HCenter;
|
||||
else if (item.equal("VCenter"))
|
||||
res |= Align.VCenter;
|
||||
else if (item.equal("Center"))
|
||||
res |= Align.Center;
|
||||
else if (item.equal("TopLeft"))
|
||||
res |= Align.TopLeft;
|
||||
else
|
||||
Log.e("unknown Align value: ", item);
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// parses string like "HotKeys|UnderlineHotKeysWhenAltPressed" to bit set of TextFlag flags
|
||||
uint decodeTextFlags(string s) {
|
||||
uint res = 0;
|
||||
int start = 0;
|
||||
for (int i = 0; i <= s.length; i++) {
|
||||
if (i == s.length || s[i] == '|') {
|
||||
if (i > start) {
|
||||
string item = s[start .. i];
|
||||
if (item.equal("HotKeys"))
|
||||
res |= TextFlag.HotKeys;
|
||||
else if (item.equal("UnderlineHotKeys"))
|
||||
res |= TextFlag.UnderlineHotKeys;
|
||||
else if (item.equal("UnderlineHotKeysWhenAltPressed"))
|
||||
res |= TextFlag.UnderlineHotKeysWhenAltPressed;
|
||||
else if (item.equal("Underline"))
|
||||
res |= TextFlag.Underline;
|
||||
else if (item.equal("Unspecified"))
|
||||
res = TEXT_FLAGS_UNSPECIFIED;
|
||||
else if (item.equal("Parent"))
|
||||
res = TEXT_FLAGS_USE_PARENT;
|
||||
else
|
||||
Log.e("unknown text flag value: ", item);
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// decode FontFamily item name to value
|
||||
FontFamily decodeFontFamily(string s) {
|
||||
if (s.equal("SansSerif"))
|
||||
return FontFamily.SansSerif;
|
||||
if (s.equal("Serif"))
|
||||
return FontFamily.Serif;
|
||||
if (s.equal("Cursive"))
|
||||
return FontFamily.Cursive;
|
||||
if (s.equal("Fantasy"))
|
||||
return FontFamily.Fantasy;
|
||||
if (s.equal("MonoSpace"))
|
||||
return FontFamily.MonoSpace;
|
||||
if (s.equal("Unspecified"))
|
||||
return FontFamily.Unspecified;
|
||||
Log.e("unknown font family ", s);
|
||||
return FontFamily.SansSerif;
|
||||
}
|
||||
|
||||
/// decode layout dimension (FILL_PARENT, WRAP_CONTENT, or just size)
|
||||
int decodeLayoutDimension(string s) {
|
||||
if (s.equal("FILL_PARENT"))
|
||||
return FILL_PARENT;
|
||||
if (s.equal("WRAP_CONTENT"))
|
||||
return WRAP_CONTENT;
|
||||
return decodeDimension(s);
|
||||
}
|
||||
|
||||
/// load style attributes from XML element
|
||||
bool loadStyleAttributes(Style style, Element elem, bool allowStates) {
|
||||
if ("backgroundImageId" in elem.tag.attr)
|
||||
style.backgroundImageId = elem.tag.attr["backgroundImageId"];
|
||||
if ("backgroundColor" in elem.tag.attr)
|
||||
style.backgroundColor = decodeHexColor(elem.tag.attr["backgroundColor"]);
|
||||
if ("textColor" in elem.tag.attr)
|
||||
style.textColor = decodeHexColor(elem.tag.attr["textColor"]);
|
||||
if ("margins" in elem.tag.attr)
|
||||
style.margins = decodeRect(elem.tag.attr["margins"]);
|
||||
if ("padding" in elem.tag.attr)
|
||||
style.padding = decodeRect(elem.tag.attr["padding"]);
|
||||
if ("align" in elem.tag.attr)
|
||||
style.alignment = decodeAlignment(elem.tag.attr["align"]);
|
||||
if ("minWidth" in elem.tag.attr)
|
||||
style.minWidth = decodeDimension(elem.tag.attr["minWidth"]);
|
||||
if ("maxWidth" in elem.tag.attr)
|
||||
style.maxWidth = decodeDimension(elem.tag.attr["maxWidth"]);
|
||||
if ("minHeight" in elem.tag.attr)
|
||||
style.minHeight = decodeDimension(elem.tag.attr["minHeight"]);
|
||||
if ("maxHeight" in elem.tag.attr)
|
||||
style.maxHeight = decodeDimension(elem.tag.attr["maxHeight"]);
|
||||
if ("fontFace" in elem.tag.attr)
|
||||
style.fontFace = elem.tag.attr["fontFace"];
|
||||
if ("fontFamily" in elem.tag.attr)
|
||||
style.fontFamily = decodeFontFamily(elem.tag.attr["fontFamily"]);
|
||||
if ("fontSize" in elem.tag.attr)
|
||||
style.fontSize = cast(ushort)decodeDimension(elem.tag.attr["fontFace"]);
|
||||
if ("layoutWidth" in elem.tag.attr)
|
||||
style.layoutWidth = decodeLayoutDimension(elem.tag.attr["layoutWidth"]);
|
||||
if ("layoutHeight" in elem.tag.attr)
|
||||
style.layoutHeight = decodeLayoutDimension(elem.tag.attr["layoutHeight"]);
|
||||
if ("alpha" in elem.tag.attr)
|
||||
style.alpha = decodeDimension(elem.tag.attr["alpha"]);
|
||||
if ("textFlags" in elem.tag.attr)
|
||||
style.textFlags = decodeTextFlags(elem.tag.attr["textFlags"]);
|
||||
foreach(item; elem.elements) {
|
||||
if (allowStates && item.tag.name.equal("state")) {
|
||||
uint stateMask = 0;
|
||||
uint stateValue = 0;
|
||||
extractStateFlags(item.tag.attr, stateMask, stateValue);
|
||||
if (stateMask) {
|
||||
Style state = style.createState(stateMask, stateValue);
|
||||
loadStyleAttributes(state, item, false);
|
||||
}
|
||||
} else if (item.tag.name.equal("drawable")) {
|
||||
// <drawable id="scrollbar_button_up" value="scrollbar_btn_up"/>
|
||||
string drawableid = attrValue(item, "id");
|
||||
string drawablevalue = attrValue(item, "value");
|
||||
if (drawableid)
|
||||
style.setCustomDrawable(drawableid, drawablevalue);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* load theme from XML document
|
||||
*
|
||||
* Sample:
|
||||
* ---
|
||||
* <?xml version="1.0" encoding="utf-8"?>
|
||||
* <theme id="theme_custom" parent="theme_default">
|
||||
* <style id="BUTTON"
|
||||
* backgroundImageId="btn_default_small"
|
||||
* >
|
||||
* </style>
|
||||
* </theme>
|
||||
* ---
|
||||
*
|
||||
*/
|
||||
bool loadTheme(Theme theme, Element doc, int level = 0) {
|
||||
if (doc.tag.name.equal("theme")) {
|
||||
Log.e("<theme> element should be main in theme file!");
|
||||
return false;
|
||||
}
|
||||
// <theme>
|
||||
string id = attrValue(doc, "id");
|
||||
string parent = attrValue(doc, "parent");
|
||||
theme.id = id;
|
||||
if (parent.length > 0) {
|
||||
// load base theme
|
||||
if (level < 3) // to prevent infinite recursion
|
||||
loadTheme(theme, parent, level + 1);
|
||||
}
|
||||
loadStyleAttributes(theme, doc, false);
|
||||
foreach(styleitem; doc.elements) {
|
||||
if (styleitem.tag.name.equal("style")) {
|
||||
// load <style>
|
||||
string styleid = attrValue(styleitem, "id");
|
||||
string styleparent = attrValue(styleitem, "parent");
|
||||
if (styleid !is null) {
|
||||
// create new style
|
||||
Style parentStyle = null;
|
||||
if (styleparent !is null)
|
||||
parentStyle = theme.get(styleparent);
|
||||
Style style = parentStyle.createSubstyle(styleid);
|
||||
loadStyleAttributes(style, styleitem, true);
|
||||
} else {
|
||||
Log.e("style without ID in theme file");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// load theme from file
|
||||
bool loadTheme(Theme theme, string resourceId, int level = 0) {
|
||||
|
||||
import std.file;
|
||||
import std.string;
|
||||
|
||||
string filename;
|
||||
try {
|
||||
filename = drawableCache.findResource(resourceId);
|
||||
if (!filename || !filename.endsWith(".xml"))
|
||||
return false;
|
||||
string s = cast(string)std.file.read(filename);
|
||||
|
||||
// Check for well-formedness
|
||||
//check(s);
|
||||
|
||||
// Make a DOM tree
|
||||
auto doc = new Document(s);
|
||||
|
||||
return loadTheme(theme, doc);
|
||||
} catch (CheckException e) {
|
||||
Log.e("Invalid XML resource ", resourceId);
|
||||
return false;
|
||||
} catch (Throwable e) {
|
||||
Log.e("Cannot read drawable resource ", resourceId, " from file ", filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// load theme from XML file (null if failed)
|
||||
Theme loadTheme(string resourceId) {
|
||||
Theme res = new Theme(resourceId);
|
||||
if (loadTheme(res, resourceId)) {
|
||||
res.id = resourceId;
|
||||
return res;
|
||||
}
|
||||
destroy(res);
|
||||
return null;
|
||||
}
|
||||
|
||||
shared static ~this() {
|
||||
currentTheme = null;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue