1 /* 2 Copyright (c) 2013 Andrey Penechko 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license the "Software" to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 module anchovy.gui.jsonguiskinparser; 30 31 import std.array; 32 import std.conv; 33 import std.json; 34 import std.stdio; 35 36 import anchovy.core.types; 37 38 import anchovy.gui; 39 40 41 //version = debug_parser; 42 43 class SkinParserException : Exception 44 { 45 this(string msg, string file = __FILE__, size_t line = __LINE__) 46 { 47 super(msg, file, line); 48 } 49 } 50 51 alias JSONValue[string] jsonObject; 52 alias JSONValue[] jsonArray; 53 54 static string[JSON_TYPE] jsonTypeNames; 55 static this() 56 { 57 jsonTypeNames = 58 [JSON_TYPE.STRING : "string", 59 JSON_TYPE.INTEGER : "int", 60 JSON_TYPE.UINTEGER : "uint", 61 JSON_TYPE.FLOAT : "float", 62 JSON_TYPE.OBJECT : "object", 63 JSON_TYPE.ARRAY : "array", 64 JSON_TYPE.TRUE : "true", 65 JSON_TYPE.FALSE : "false", 66 JSON_TYPE.NULL : "null",]; 67 } 68 69 static const string returnDefault = "if (value.type == JSON_TYPE.NULL) return defaultValue;"; 70 71 class JsonGuiSkinParser 72 { 73 string[] warnings; 74 string[] errors; 75 76 void warn(string message) 77 { 78 warnings ~= message; 79 } 80 81 GuiSkin parse(string skinData) 82 { 83 GuiSkin skin = new GuiSkin(); 84 85 try 86 { 87 auto jsonValue = parseJSON(skinData); 88 89 skin.name = getValue!string("name", jsonValue); 90 skin.textureFilename = getValue!string("image", jsonValue); 91 skin.fontInfos = parseFonts(getValue!jsonArray("fonts", jsonValue)); 92 93 foreach(i, ref value; getValue!jsonArray("styles", jsonValue)) 94 { 95 string styleName = getValue!string("name", value); 96 97 if (styleName == "") 98 { 99 warn("No style name was specified for style no " ~ to!string(i + 1) ~ " in skin " ~ skin.name); 100 continue; 101 } 102 103 skin.styles[styleName] = parseStyle(value, styleName); 104 } 105 106 } 107 catch(JSONException exception) 108 { 109 throw new SkinParserException(exception.msg); 110 } 111 112 if (!warnings.empty) 113 { 114 foreach(warning; warnings) 115 writeln("Warning: ", warning); 116 117 warnings = []; 118 } 119 120 return skin; 121 } 122 123 /** 124 * len Result 125 * 1 Поля будут установлены одновременно от каждого края элемента. 126 * 2 Первое значение устанавливает поля от верхнего и нижнего края, второе — от левого и правого. 127 * 3 Первое значение задает поле от верхнего края, второе — одновременно от левого и правого края, а третье — от нижнего края. 128 * 4 Поочередно устанавливается поля от верхнего, правого, нижнего и левого края. 129 */ 130 131 GuiStyle parseStyle(ref JSONValue value, string styleName) 132 { 133 expect(JSON_TYPE.OBJECT, value); 134 135 GuiStyle parsedStyle = new GuiStyle(); 136 GuiStyleState nullState; 137 138 parsedStyle.states["normal"] = parseStyleState(value, nullState); 139 parsedStyle.fontName = getValue!string("font", value, "normal"); 140 141 jsonArray states = getValue!jsonArray("states", value); 142 143 foreach(i, ref state; states) 144 { 145 string stateName = getValue!string("state", state); 146 147 if (stateName == "") 148 { 149 warn("No state name was specified for state no "~to!string(i+1)~" in style "~styleName); 150 continue; 151 } 152 153 version(debug_parser) writeln("stateName: ", stateName); 154 155 parsedStyle.states[stateName] = parseStyleState(state, parsedStyle.states["normal"]); 156 } 157 158 return parsedStyle; 159 } 160 161 GuiStyleState parseStyleState(ref JSONValue stateValue, ref GuiStyleState globalState) 162 { 163 GuiStyleState parsedStyleState; 164 expect(JSON_TYPE.OBJECT, stateValue); 165 166 jsonArray fixedBordersValue = getValue!jsonArray("fixedBorders", stateValue); 167 parsedStyleState.fixedBorders = RectOffset(parseRectOffset(fixedBordersValue, globalState.fixedBorders.arrayof)); 168 169 jsonArray contentPaddingValue = getValue!jsonArray("contentPadding", stateValue); 170 parsedStyleState.contentPadding = RectOffset(parseRectOffset(contentPaddingValue, globalState.contentPadding.arrayof)); 171 172 jsonArray rectValue = getValue!jsonArray("rect", stateValue); 173 parsedStyleState.atlasRect = Rect(parseRect(rectValue, globalState.atlasRect.arrayof)); 174 175 jsonArray outlineValue = getValue!jsonArray("outline", stateValue); 176 parsedStyleState.outline = RectOffset(parseRectOffset(outlineValue, globalState.outline.arrayof)); 177 178 jsonArray minSize = getValue!jsonArray("minSize", stateValue); 179 parsedStyleState.minSize = parseSize(minSize, ivec2(parsedStyleState.atlasRect.width, parsedStyleState.atlasRect.height)); 180 181 jsonArray maxSize = getValue!jsonArray("maxSize", stateValue); 182 parsedStyleState.maxSize = parseSize(maxSize, ivec2(0, 0)); 183 184 return parsedStyleState; 185 } 186 187 FontInfo[] parseFonts(jsonArray inArray) 188 { 189 FontInfo[] outFonts; 190 191 foreach(font; inArray) 192 { 193 outFonts ~= FontInfo(getValue!string("name", font), 194 getValue!string("file", font), 195 getValue!uint("size", font), 196 getValue!int("verticalOffset", font)); 197 } 198 199 version(debug_parser) writeln(outFonts); 200 201 return outFonts; 202 } 203 204 private: 205 206 int[4] parseRect(jsonArray inArray, int[4] defaultValue = [0, 0, 0, 0]) 207 { 208 if (inArray.length == 0) return defaultValue; 209 210 int[4] outArray; 211 212 foreach(i, ref element; inArray) 213 { 214 if (i > 3) 215 { 216 warn("Rect with more than 4 values found"); 217 break; 218 } 219 220 outArray[i] = getValue!int(element); 221 } 222 223 return outArray; 224 } 225 226 ivec2 parsePoint(jsonArray inArray, ivec2 defaultValue = ivec2(0, 0)) 227 { 228 if (inArray.length == 0) 229 return defaultValue; 230 231 if (inArray.length == 1) 232 return ivec2(getValue!int(inArray[0]), 0); 233 234 return ivec2(getValue!int(inArray[0]), getValue!int(inArray[1])); 235 } 236 237 ivec2 parseSize(jsonArray inArray, ivec2 defaultValue = ivec2(0, 0)) 238 { 239 if (inArray.length == 0) 240 return defaultValue; 241 242 if (inArray.length == 1) 243 { 244 uint size = getValue!uint(inArray[0]); 245 246 return ivec2(size, size); 247 } 248 249 return ivec2(getValue!uint(inArray[0]), getValue!uint(inArray[1])); 250 } 251 252 int[4] parseRectOffset(jsonArray inArray, int[4] defaultValue = [0, 0, 0, 0]) 253 { 254 if (inArray.length == 0) 255 { 256 return defaultValue; 257 } 258 else if (inArray.length == 1) 259 { 260 int val = getValue!int(inArray[0]); 261 262 return [val, val, val, val]; 263 } 264 else if (inArray.length == 2) 265 { 266 int vert = getValue!int(inArray[0]); 267 int hor = getValue!int(inArray[1]); 268 269 return [hor, hor, vert, vert]; 270 } 271 else if (inArray.length == 3) 272 { 273 int top = getValue!int(inArray[0]); 274 int hor = getValue!int(inArray[1]); 275 int bottom = getValue!int(inArray[2]); 276 277 return [hor, hor, top, bottom]; 278 } 279 else 280 { 281 int top = getValue!int(inArray[0]); 282 int right = getValue!int(inArray[1]); 283 int bottom = getValue!int(inArray[2]); 284 int left = getValue!int(inArray[3]); 285 286 if (inArray.length > 4) warn("Rect with more than 4 values found"); 287 288 return [left, right, top, bottom]; 289 } 290 } 291 292 void expect(JSON_TYPE type, ref JSONValue value, string file = __FILE__, size_t line = __LINE__) 293 { 294 if(value.type != type && value.type != JSON_TYPE.NULL) 295 { 296 throw new SkinParserException("Wrong JSON type. " ~ 297 jsonTypeNames[value.type] ~ 298 " found while " ~ 299 jsonTypeNames[type] ~ 300 " expected", file, line); 301 } 302 } 303 304 T getValue(T)(string key, ref JSONValue objectValue, T defaultValue = T.init) 305 { 306 JSONValue* targetValue = key in objectValue.object; 307 308 if (targetValue is null) 309 { 310 return defaultValue; 311 } 312 else 313 { 314 return getValue!T(*targetValue, defaultValue); 315 } 316 } 317 318 T getValue(T)(ref JSONValue value, T defaultValue = T.init) 319 { 320 static if (is(T : string)) 321 { 322 expect(JSON_TYPE.STRING, value); 323 mixin(returnDefault); 324 return value.str; 325 } 326 else static if (is(T : int)) 327 { 328 expect(JSON_TYPE.INTEGER, value); 329 mixin(returnDefault); 330 return cast(int)value.integer; 331 } 332 else static if (is(T : uint)) 333 { 334 expect(JSON_TYPE.UINTEGER, value); 335 mixin(returnDefault); 336 return cast(uint)value.uinteger; 337 } 338 else static if(is(T : float)) 339 { 340 expect(JSON_TYPE.FLOAT, value); 341 mixin(returnDefault); 342 return cast(float)value.floating; 343 } 344 else static if(is(T : jsonObject)) 345 { 346 expect(JSON_TYPE.OBJECT, value); 347 mixin(returnDefault); 348 return value.object; 349 } 350 else static if(is(T : jsonArray)) 351 { 352 expect(JSON_TYPE.ARRAY, value); 353 mixin(returnDefault); 354 return value.array; 355 } 356 else static if(is(T : bool)) 357 { 358 if(value.type == JSON_TYPE.TRUE) 359 return true; 360 else if (value.type == JSON_TYPE.FALSE) 361 return false; 362 else if (value.type == JSON_TYPE.NULL) 363 return defaultValue; 364 else throw new SkinParserException("Wrong JSON type. " ~ 365 jsonTypeNames[value.type] ~ 366 " found while boolean expected"); 367 } 368 } 369 }