VisualTheme.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2018 to 2022 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #include <stdint.h>
  24. #include "VisualTheme.h"
  25. #include "../api/imageAPI.h"
  26. #include "../api/drawAPI.h"
  27. #include "../api/mediaMachineAPI.h"
  28. #include "../api/configAPI.h"
  29. namespace dsr {
  30. // The default theme
  31. // Copy and modify and compile with theme_create to get a custom theme
  32. static const ReadableString defaultMediaMachineCode =
  33. UR"QUOTE(
  34. # Helper methods
  35. BEGIN: generate_rounded_rectangle
  36. # Dimensions of the result image
  37. INPUT: FixedPoint, width
  38. INPUT: FixedPoint, height
  39. # The whole pixel radius from center points to the end of the image
  40. INPUT: FixedPoint, corner
  41. # The subtracted offset from the radius to create a border on certain channels
  42. INPUT: FixedPoint, border
  43. # Create the result image
  44. OUTPUT: ImageU8, resultImage
  45. CREATE: resultImage, width, height
  46. # Limit outer radius to half of the image's minimum dimension
  47. MIN: radius<FixedPoint>, width, height
  48. MUL: radius, radius, 0.5
  49. MIN: radius, radius, corner
  50. ROUND: radius, radius
  51. # Place the inner radius for drawing
  52. SUB: innerRadius<FixedPoint>, corner, border
  53. # Use +- 0.5 pixel offsets for fake anti-aliasing
  54. ADD: radiusOut<FixedPoint>, innerRadius, 0.5
  55. ADD: radiusIn<FixedPoint>, innerRadius, -0.5
  56. # Calculate dimensions for drawing
  57. SUB: w2<FixedPoint>, width, radius
  58. SUB: w3<FixedPoint>, w2, radius
  59. SUB: w4<FixedPoint>, width, border
  60. SUB: w4, w4, border
  61. SUB: h2<FixedPoint>, height, radius
  62. SUB: h3<FixedPoint>, h2, radius
  63. SUB: r2<FixedPoint>, radius, border
  64. # Draw
  65. FADE_REGION_RADIAL: resultImage, 0, 0, radius, radius, radius, radius, radiusIn, 255, radiusOut, 0
  66. FADE_REGION_RADIAL: resultImage, w2, 0, radius, radius, 0, radius, radiusIn, 255, radiusOut, 0
  67. FADE_REGION_RADIAL: resultImage, 0, h2, radius, radius, radius, 0, radiusIn, 255, radiusOut, 0
  68. FADE_REGION_RADIAL: resultImage, w2, h2, radius, radius, 0, 0, radiusIn, 255, radiusOut, 0
  69. RECTANGLE: resultImage, radius, border, w3, r2, 255
  70. RECTANGLE: resultImage, radius, h2, w3, r2, 255
  71. RECTANGLE: resultImage, border, radius, w4, h3, 255
  72. END:
  73. BEGIN: generate_rounded_button
  74. INPUT: FixedPoint, width
  75. INPUT: FixedPoint, height
  76. INPUT: FixedPoint, red
  77. INPUT: FixedPoint, green
  78. INPUT: FixedPoint, blue
  79. INPUT: FixedPoint, pressed
  80. INPUT: FixedPoint, radius
  81. INPUT: FixedPoint, border
  82. OUTPUT: ImageRgbaU8, resultImage
  83. # Scale by 2 / 255 so that 127.5 represents full intensity in patternImage
  84. MUL: normRed<FixedPoint>, red, 0.007843138
  85. MUL: normGreen<FixedPoint>, green, 0.007843138
  86. MUL: normBlue<FixedPoint>, blue, 0.007843138
  87. CREATE: patternImage<ImageU8>, width, height
  88. MUL: pressDarknessHigh<FixedPoint>, pressed, 80
  89. MUL: pressDarknessLow<FixedPoint>, pressed, 10
  90. SUB: highLuma<FixedPoint>, 150, pressDarknessHigh
  91. SUB: lowLuma<FixedPoint>, 100, pressDarknessLow
  92. FADE_LINEAR: patternImage, 0, 0, highLuma, 0, height, lowLuma
  93. CALL: generate_rounded_rectangle, lumaImage<ImageU8>, width, height, radius, border
  94. MUL: lumaImage, lumaImage, patternImage, 0.003921569
  95. CALL: generate_rounded_rectangle, visImage<ImageU8>, width, height, radius, 0
  96. MUL: redImage<ImageU8>, lumaImage, normRed
  97. MUL: greenImage<ImageU8>, lumaImage, normGreen
  98. MUL: blueImage<ImageU8>, lumaImage, normBlue
  99. PACK_RGBA: resultImage, redImage, greenImage, blueImage, visImage
  100. END:
  101. BEGIN: Button
  102. INPUT: FixedPoint, width
  103. INPUT: FixedPoint, height
  104. INPUT: FixedPoint, red
  105. INPUT: FixedPoint, green
  106. INPUT: FixedPoint, blue
  107. INPUT: FixedPoint, pressed
  108. INPUT: FixedPoint, Button_borderThickness
  109. INPUT: FixedPoint, Button_rounding
  110. OUTPUT: ImageRgbaU8, colorImage
  111. CALL: generate_rounded_button, colorImage, width, height, red, green, blue, pressed, Button_rounding, Button_borderThickness
  112. END:
  113. BEGIN: ListBox
  114. INPUT: FixedPoint, width
  115. INPUT: FixedPoint, height
  116. INPUT: FixedPoint, red
  117. INPUT: FixedPoint, green
  118. INPUT: FixedPoint, blue
  119. INPUT: FixedPoint, ListBox_borderThickness
  120. OUTPUT: ImageRgbaU8, colorImage
  121. CREATE: colorImage, width, height
  122. ADD: b2<FixedPoint>, ListBox_borderThickness, ListBox_borderThickness
  123. SUB: w2<FixedPoint>, width, b2
  124. SUB: h2<FixedPoint>, height, b2
  125. RECTANGLE: colorImage, ListBox_borderThickness, ListBox_borderThickness, w2, h2, red, green, blue, 255
  126. END:
  127. BEGIN: ScrollTop
  128. INPUT: FixedPoint, width
  129. INPUT: FixedPoint, height
  130. INPUT: FixedPoint, red
  131. INPUT: FixedPoint, green
  132. INPUT: FixedPoint, blue
  133. INPUT: FixedPoint, pressed
  134. INPUT: FixedPoint, Scroll_borderThickness
  135. INPUT: FixedPoint, Scroll_button_rounding
  136. OUTPUT: ImageRgbaU8, colorImage
  137. CALL: generate_rounded_button, colorImage, width, height, red, green, blue, pressed, Scroll_button_rounding, Scroll_borderThickness
  138. END:
  139. BEGIN: ScrollBottom
  140. INPUT: FixedPoint, width
  141. INPUT: FixedPoint, height
  142. INPUT: FixedPoint, red
  143. INPUT: FixedPoint, green
  144. INPUT: FixedPoint, blue
  145. INPUT: FixedPoint, pressed
  146. INPUT: FixedPoint, Scroll_borderThickness
  147. INPUT: FixedPoint, Scroll_button_rounding
  148. OUTPUT: ImageRgbaU8, colorImage
  149. CALL: generate_rounded_button, colorImage, width, height, red, green, blue, pressed, Scroll_button_rounding, Scroll_borderThickness
  150. END:
  151. BEGIN: VerticalScrollKnob
  152. INPUT: FixedPoint, width
  153. INPUT: FixedPoint, height
  154. INPUT: FixedPoint, red
  155. INPUT: FixedPoint, green
  156. INPUT: FixedPoint, blue
  157. INPUT: FixedPoint, pressed
  158. INPUT: FixedPoint, Scroll_borderThickness
  159. INPUT: FixedPoint, Scroll_knob_rounding
  160. OUTPUT: ImageRgbaU8, colorImage
  161. CALL: generate_rounded_button, colorImage, width, height, red, green, blue, pressed, Scroll_knob_rounding, Scroll_borderThickness
  162. END:
  163. BEGIN: VerticalScrollBackground
  164. INPUT: FixedPoint, width
  165. INPUT: FixedPoint, height
  166. INPUT: FixedPoint, red
  167. INPUT: FixedPoint, green
  168. INPUT: FixedPoint, blue
  169. OUTPUT: ImageRgbaU8, colorImage
  170. CREATE: visImage<ImageU8>, width, height
  171. CREATE: lumaImage<ImageU8>, width, height
  172. FADE_LINEAR: visImage, 0, 0, 128, width, 0, 0
  173. PACK_RGBA: colorImage, 0, 0, 0, visImage
  174. END:
  175. BEGIN: Panel
  176. INPUT: FixedPoint, width
  177. INPUT: FixedPoint, height
  178. INPUT: FixedPoint, red
  179. INPUT: FixedPoint, green
  180. INPUT: FixedPoint, blue
  181. INPUT: FixedPoint, Panel_borderThickness
  182. OUTPUT: ImageRgbaU8, colorImage
  183. CREATE: colorImage, width, height
  184. ADD: b2<FixedPoint>, Panel_borderThickness, Panel_borderThickness
  185. SUB: w2<FixedPoint>, width, b2
  186. SUB: h2<FixedPoint>, height, b2
  187. RECTANGLE: colorImage, Panel_borderThickness, Panel_borderThickness, w2, h2, red, green, blue, 255
  188. END:
  189. )QUOTE";
  190. // Using *.ini files for storing style settings as a simple start.
  191. // A more advanced system will be used later.
  192. static const ReadableString defaultStyleSettings =
  193. UR"QUOTE(
  194. Button_borderThickness = 2
  195. Button_rounding = 12
  196. ListBox_borderThickness = 2
  197. Scroll_borderThickness = 2
  198. Scroll_button_rounding = 5
  199. Scroll_knob_rounding = 8
  200. Panel_borderThickness = 1
  201. )QUOTE";
  202. template <typename V>
  203. struct KeywordEntry {
  204. String key;
  205. V value;
  206. KeywordEntry(const ReadableString &key, const V &value)
  207. : key(key), value(value) {}
  208. };
  209. struct StyleSettings {
  210. List<KeywordEntry<AlignedImageU8>> monochromeImages;
  211. List<KeywordEntry<OrderedImageRgbaU8>> colorImages;
  212. List<KeywordEntry<FixedPoint>> scalars;
  213. StyleSettings(const ReadableString &styleSettings) {
  214. config_parse_ini(styleSettings, [this](const ReadableString& block, const ReadableString& key, const ReadableString& value) {
  215. // Assuming that everything is a scalar in the global context until a more advanced parser has been implemented.
  216. // TODO: Should blocks be used for internal style variations with multiple ini files per theme?
  217. // Or can blocks be used for both internal style and user selected styles?
  218. // Maybe selecting color presets separatelly is enough for user preferences.
  219. if (string_length(block) == 0) {
  220. this->scalars.pushConstruct(key, FixedPoint::fromText(value));
  221. }
  222. });
  223. }
  224. };
  225. class VisualThemeImpl {
  226. public:
  227. MediaMachine machine;
  228. StyleSettings settings;
  229. explicit VisualThemeImpl(const ReadableString& mediaCode, const ReadableString &styleCode) : machine(machine_create(mediaCode)), settings(styleCode) {}
  230. // Destructor
  231. virtual ~VisualThemeImpl() {}
  232. };
  233. static VisualTheme defaultTheme;
  234. VisualTheme theme_getDefault() {
  235. if (!(defaultTheme.get())) {
  236. defaultTheme = theme_create(defaultMediaMachineCode, defaultStyleSettings);
  237. }
  238. return defaultTheme;
  239. }
  240. VisualTheme theme_create(const ReadableString& mediaCode, const ReadableString& styleSettings) {
  241. return std::make_shared<VisualThemeImpl>(mediaCode, styleSettings);
  242. }
  243. MediaMethod theme_getScalableImage(const VisualTheme& theme, const ReadableString &name) {
  244. return machine_getMethod(theme->machine, name);
  245. }
  246. bool theme_assignMediaMachineArguments(const VisualTheme& theme, MediaMachine &machine, int methodIndex, int inputIndex, const ReadableString &argumentName) {
  247. if (theme.get()) {
  248. // Search for argumentName in monochromeImages.
  249. for (int64_t i = 0; i < theme->settings.monochromeImages.length(); i++) {
  250. if (string_caseInsensitiveMatch(theme->settings.monochromeImages[i].key, argumentName)) {
  251. machine_setInputByIndex(machine, methodIndex, inputIndex, theme->settings.monochromeImages[i].value);
  252. return true;
  253. }
  254. }
  255. // Search for argumentName in colorImages.
  256. for (int64_t i = 0; i < theme->settings.colorImages.length(); i++) {
  257. if (string_caseInsensitiveMatch(theme->settings.colorImages[i].key, argumentName)) {
  258. machine_setInputByIndex(machine, methodIndex, inputIndex, theme->settings.colorImages[i].value);
  259. return true;
  260. }
  261. }
  262. // Search for argumentName in scalars.
  263. for (int64_t i = 0; i < theme->settings.scalars.length(); i++) {
  264. if (string_caseInsensitiveMatch(theme->settings.scalars[i].key, argumentName)) {
  265. machine_setInputByIndex(machine, methodIndex, inputIndex, theme->settings.scalars[i].value);
  266. return true;
  267. }
  268. }
  269. }
  270. return false;
  271. }
  272. }