1 module poison.ui.styles; 2 3 import std.file : readText; 4 import std.json : parseJSON; 5 import std.conv : to; 6 import std.array : split, replace; 7 import std.string : toLower; 8 import std.algorithm : strip, stripLeft, stripRight, countUntil; 9 import std.base64 : Base64; 10 11 import poison.ui.graphics; 12 import poison.ui.paint; 13 import poison.ui.picture; 14 import poison.ui.fonts; 15 16 /// Table of styles. 17 private Graphics[string] _styles; 18 19 /** 20 * Gets a style entry. 21 * Params: 22 * selector = The selector to find the style for. 23 */ 24 Graphics getStyleEntry(string selector) { 25 return _styles ? _styles.get(selector, null) : null; 26 } 27 28 /** 29 * Loads a style sheet. 30 * Params: 31 * The style sheet to load. 32 */ 33 void loadStyleSheet(string styleSheet) { 34 auto jsonText = readText(styleSheet); 35 36 loadStyles(jsonText); 37 } 38 39 /** 40 * Loads styles from json structured pss. 41 * Params: 42 * jsonText = The json. 43 */ 44 void loadStyles(string jsonText) { 45 if (!jsonText) { 46 return; 47 } 48 49 auto json = parseJSON("{ " ~ jsonText ~ " }"); 50 auto entries = json.object; 51 52 if (!entries) { 53 return; 54 } 55 56 foreach (selector,entryJson; entries) { 57 auto styles = entryJson.object; 58 59 if (styles) { 60 auto graphics = new Graphics(); 61 62 foreach (styleName,styleValue; styles) { 63 auto value = styleValue.str; 64 65 assert(value); 66 67 final switch (styleName) { 68 case "background": { 69 handleGroups(graphics, value, (gfx, propertyName, propertyValue) { 70 final switch (propertyName) { 71 case "color": { 72 gfx.backgroundPaint = handlePaintInput(propertyValue); 73 break; 74 } 75 76 case "image": { 77 gfx.backgroundPicture = handleImageInput(propertyValue); 78 break; 79 } 80 } 81 }); 82 break; 83 } 84 85 case "background-color": { 86 graphics.backgroundPaint = handlePaintInput(value); 87 break; 88 } 89 90 case "background-image": { 91 graphics.backgroundPicture = handleImageInput(value); 92 break; 93 } 94 95 case "foreground-color": { 96 graphics.foregroundPaint = handlePaintInput(value); 97 break; 98 } 99 100 case "font": { 101 handleGroups(graphics, value, (gfx, propertyName, propertyValue) { 102 final switch (propertyName) { 103 case "name": { 104 gfx.font = handleFontInput(propertyValue); 105 break; 106 } 107 108 case "path": { 109 gfx.font = loadFont(propertyValue); 110 break; 111 } 112 113 case "size": { 114 gfx.fontSize = to!uint(propertyValue); 115 break; 116 } 117 } 118 }); 119 break; 120 } 121 122 case "font-name": { 123 graphics.font = handleFontInput(value); 124 break; 125 } 126 127 case "font-path": { 128 graphics.font = loadFont(value); 129 break; 130 } 131 132 case "font-size": { 133 graphics.fontSize = to!uint(value); 134 } 135 } 136 } 137 138 _styles[selector] = graphics; 139 } 140 } 141 142 import poison.core : Application; 143 144 auto app = Application.app; 145 146 if (!app) { 147 return; 148 } 149 150 app.updateStyles(); 151 } 152 153 private: 154 /** 155 * Handles group values. 156 * Params: 157 * graphics = The graphics to handle values for. 158 * value = The value to parse groups from. 159 * predicate = A predicate to mutate the graphics. 160 */ 161 void handleGroups(Graphics graphics, string value, void delegate(Graphics, string, string) predicate) { 162 assert(predicate !is null); 163 164 auto groups = value.split("|"); 165 166 foreach (group; groups) { 167 auto separatorIndex = countUntil(group, ":"); 168 auto propertyName = group[0 .. separatorIndex]; 169 auto propertyValue = group[separatorIndex + 1 .. $]; 170 171 predicate(graphics, propertyName, propertyValue); 172 } 173 } 174 175 /** 176 * Handles group values. 177 * Params: 178 * graphics = The graphics to handle values for. 179 * value = The value to parse groups from. 180 * predicate = A predicate to mutate the graphics. 181 */ 182 void handleGroups(Graphics graphics, string value, void function(Graphics, string, string) predicate) { 183 assert(predicate !is null); 184 185 auto groups = value.split("|"); 186 187 foreach (group; groups) { 188 auto separatorIndex = countUntil(group, ":"); 189 auto propertyName = group[0 .. separatorIndex]; 190 auto propertyValue = group[separatorIndex + 1 .. $]; 191 192 predicate(graphics, propertyName, propertyValue); 193 } 194 } 195 196 /** 197 * Handles paint input. 198 * Params: 199 * value = The value of the paint input. 200 * Returns: 201 * The generated paint from the value. 202 */ 203 Paint handlePaintInput(string value) { 204 if (value[0] == '#') { 205 value = value[1 .. $].toLower(); 206 207 if (value.length == 3 && value[0] == value[1] && value[0] == value[2]) { 208 value ~= value; 209 } 210 211 if (value.length == 6) { 212 value ~= "ff"; 213 } 214 215 auto values = [value[0 .. 2], value[2 .. 4], value[4 .. 6], value[6 .. $]]; 216 217 ubyte r = to!ubyte(values[0], 16); 218 ubyte g = to!ubyte(values[1], 16); 219 ubyte b = to!ubyte(values[2], 16); 220 ubyte a = to!ubyte(values[3], 16); 221 222 return paintFromRGBA(r, g, b, a); 223 } 224 else { 225 auto values = value.split(";"); 226 227 if (values.length > 1) { 228 assert(values.length == 3 || values.length == 4); 229 230 ubyte r = to!ubyte(values[0]); 231 ubyte g = to!ubyte(values[1]); 232 ubyte b = to!ubyte(values[2]); 233 ubyte a = 0xff; 234 235 if (values.length == 4) { 236 a = to!ubyte(values[3]); 237 } 238 239 return paintFromRGBA(r, g, b, a); 240 } 241 242 return paintFromName(value); 243 } 244 } 245 246 /** 247 * Handles image inputs. 248 * Params: 249 * value = The image input. 250 * Returns: 251 * The generated picture from the value. 252 */ 253 Picture handleImageInput(string value) { 254 auto data = value.split(":"); 255 256 if (data.length == 1) { 257 return new Picture(value); 258 } 259 else { 260 assert(data.length == 2); 261 262 auto propertyName = data[0]; 263 auto propertyValue = data[1]; 264 265 final switch (propertyName) { 266 case "base64": { 267 auto buffer = Base64.decode(propertyValue); 268 269 return new Picture(buffer); 270 } 271 } 272 } 273 } 274 275 /** 276 * Handles font input. 277 * Params: 278 * value = The font input value. 279 */ 280 Font handleFontInput(string value) { 281 auto values = value.split(";"); 282 auto fontName = values[0]; 283 284 if (values.length == 2) { 285 return retrieveFont(fontName, to!FontStyle(values[1])); 286 } 287 288 return retrieveFont(fontName, FontStyle.normal); 289 }