My Marlin configs for Fabrikator Mini and CTC i3 Pro B
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

poly_ui.h 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /*************
  2. * poly_ui.h *
  3. *************/
  4. /****************************************************************************
  5. * Written By Marcio Teixeira 2019 - Aleph Objects, Inc. *
  6. * *
  7. * This program is free software: you can redistribute it and/or modify *
  8. * it under the terms of the GNU General Public License as published by *
  9. * the Free Software Foundation, either version 3 of the License, or *
  10. * (at your option) any later version. *
  11. * *
  12. * This program is distributed in the hope that it will be useful, *
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  15. * GNU General Public License for more details. *
  16. * *
  17. * To view a copy of the GNU General Public License, go to the following *
  18. * location: <https://www.gnu.org/licenses/>. *
  19. ****************************************************************************/
  20. #pragma once
  21. /**
  22. * The PolyReader class iterates over an array of (x,y) pairs.
  23. * For supporting polygons with holes an end-of-loop marker may
  24. * be embedded into the data stream:
  25. *
  26. * const PROGMEM uint16_t data[] = {
  27. * x, y, x, y, ..., eol,
  28. * ...
  29. * x, y, x, y, ..., eol
  30. * }
  31. *
  32. * The PolyReader object can be used to iterate over the points.
  33. *
  34. * PolyReader r(data, N_ELEMENTS(data));
  35. *
  36. * for (r.start();r.has_more(); r.next()) {
  37. * uint16_t x = r.x;
  38. * uint16_t y = r.y;
  39. *
  40. * // Do something with the point
  41. * ...
  42. *
  43. * // Do something else if this point
  44. * // closes a loop.
  45. * if (r.end_of_loop()) {
  46. * ...
  47. * }
  48. * }
  49. */
  50. class PolyReader {
  51. public:
  52. typedef uint16_t type_t;
  53. private:
  54. static constexpr type_t eol = 0xFFFF;
  55. const type_t *p, *top, *end;
  56. type_t start_x, start_y;
  57. void close_loop() {
  58. x = start_x;
  59. y = start_y;
  60. start_x = eol;
  61. start_y = eol;
  62. }
  63. public:
  64. type_t x, y;
  65. // Begin reading a polygon data structure
  66. PolyReader(const uint16_t data[], const size_t n_elements) : top(data), end(data + n_elements) {
  67. start();
  68. }
  69. void start() {
  70. p = top;
  71. start_x = eol;
  72. next();
  73. }
  74. // Reads the next point in the polygon data structure
  75. void next() {
  76. if (!p) return;
  77. if (p == end) {
  78. if (start_x != eol)
  79. close_loop();
  80. else
  81. p = nullptr;
  82. }
  83. else {
  84. x = pgm_read_word_far(p++);
  85. if (x == eol)
  86. close_loop();
  87. else {
  88. y = pgm_read_word_far(p++);
  89. if (start_x == eol) {
  90. start_x = x;
  91. start_y = y;
  92. }
  93. }
  94. }
  95. }
  96. bool has_more() { return p != nullptr; }
  97. bool end_of_loop() { return start_x == eol; }
  98. };
  99. /**
  100. * The TransformedPolyReader class works like the PolyReader,
  101. * but the (x,y) input is assumed to be normalized onto a
  102. * unit square and then mapped to the full 16-bits, i.e.
  103. * (0.0,1.0) => (0x0000,0xFFFE). This class will scale the
  104. * data to fit the entire display, a bounding box, or apply
  105. * some arbitrary affine transform.
  106. *
  107. * This class is suitable for reading data from "svg2cpp.py"
  108. */
  109. class TransformedPolyReader : public PolyReader {
  110. private:
  111. /**
  112. * Fixed point type for fast transformations, supports
  113. * values from 0 to 1024, with 1/32 precision.
  114. */
  115. static constexpr uint8_t fract_bits = 5;
  116. typedef int16_t fix_t;
  117. fix_t makefix(float f) { return f * (1 << fract_bits); }
  118. // First two rows of 3x3 transformation matrix
  119. fix_t a, b, c;
  120. fix_t d, e, f;
  121. void transform() {
  122. /**
  123. * Values from PolyReader vary from 0 to FFFE.
  124. * As an approximation to dividing by FFFE,
  125. * we perform a bit shift right by 16.
  126. */
  127. const int32_t px = PolyReader::x;
  128. const int32_t py = PolyReader::y;
  129. const int32_t round = 1 << (fract_bits-1);
  130. x = (((((a * px) + (b * py)) >> 16) + c) + round) >> fract_bits;
  131. y = (((((d * px) + (e * py)) >> 16) + f) + round) >> fract_bits;
  132. }
  133. void set_transform(
  134. fix_t A, fix_t B, fix_t C,
  135. fix_t D, fix_t E, fix_t F
  136. ) {
  137. a = A; b = B; c = C;
  138. d = D; e = E; f = F;
  139. }
  140. public:
  141. typedef int16_t type_t;
  142. type_t x, y;
  143. TransformedPolyReader(const uint16_t data[], const size_t n) : PolyReader(data, n) {
  144. scale_to_fit();
  145. transform();
  146. }
  147. // Set an arbitrary affine transform
  148. void set_transform(
  149. float A, float B, float C,
  150. float D, float E, float F
  151. ) {
  152. set_transform(
  153. makefix(A), makefix(B), makefix(C),
  154. makefix(D), makefix(E), makefix(F)
  155. );
  156. }
  157. // Scale the data to fit a specified bounding box
  158. void scale_to_fit(type_t x_min, type_t y_min, type_t x_max, type_t y_max) {
  159. fix_t sx = makefix(x_max - x_min);
  160. fix_t sy = makefix(y_max - y_min);
  161. fix_t tx = makefix(x_min);
  162. fix_t ty = makefix(y_min);
  163. set_transform(
  164. sx, 0, tx,
  165. 0, sy, ty
  166. );
  167. }
  168. // Scale to fit the entire display (default)
  169. void scale_to_fit() {
  170. scale_to_fit(0, 0, FTDI::display_width, FTDI::display_height);
  171. }
  172. void next() {
  173. PolyReader::next();
  174. transform();
  175. }
  176. };
  177. /**
  178. * The DeduplicatedPolyReader wraps around another PolyReader
  179. * class to remove repeated points from the data. This could
  180. * happen when scaling down using TransformedPolyReader, for
  181. * example.
  182. */
  183. template<class POLY_READER>
  184. class DeduplicatedPolyReader : public POLY_READER {
  185. private:
  186. typename POLY_READER::type_t last_x, last_y;
  187. static constexpr typename POLY_READER::type_t eol = 0xFFFF;
  188. public:
  189. DeduplicatedPolyReader(const uint16_t data[], const size_t n) : POLY_READER(data, n) {
  190. last_x = POLY_READER::x;
  191. last_y = POLY_READER::y;
  192. }
  193. void next() {
  194. do {
  195. if (!POLY_READER::has_more()) return;
  196. POLY_READER::next();
  197. } while (POLY_READER::x == last_x && POLY_READER::y == last_y && !POLY_READER::end_of_loop());
  198. if (POLY_READER::end_of_loop()) {
  199. last_x = last_y = eol;
  200. }
  201. else {
  202. last_x = POLY_READER::x;
  203. last_y = POLY_READER::y;
  204. }
  205. }
  206. };
  207. /**
  208. * The helper class allows you to build an interface based on arbitrary
  209. * shapes.
  210. */
  211. template<class POLY_READER=DeduplicatedPolyReader<TransformedPolyReader>>
  212. class GenericPolyUI {
  213. protected:
  214. CommandProcessor &cmd;
  215. draw_mode_t mode;
  216. private:
  217. // Attributes used to paint buttons
  218. uint32_t btn_fill_color = 0x000000;
  219. uint32_t btn_shadow_color = 0xF3E0E0;
  220. uint8_t btn_shadow_depth = 5;
  221. uint32_t btn_stroke_color = 0x000000;
  222. uint8_t btn_stroke_width = 28;
  223. public:
  224. enum ButtonStyle : uint8_t {
  225. FILL = 1,
  226. STROKE = 2,
  227. SHADOW = 4,
  228. REGULAR = 7
  229. };
  230. typedef POLY_READER poly_reader_t;
  231. GenericPolyUI(CommandProcessor &c, draw_mode_t what = BOTH) : cmd(c), mode(what) {}
  232. // Fills a polygon with the current COLOR_RGB
  233. void fill(poly_reader_t r, bool clip = true) {
  234. using namespace FTDI;
  235. int16_t x, y, w, h;
  236. if (clip) {
  237. // Clipping reduces the number of pixels that are
  238. // filled, allowing more complex shapes to be drawn
  239. // in the allotted time.
  240. bounds(r, x, y, w, h);
  241. cmd.cmd(SAVE_CONTEXT());
  242. cmd.cmd(SCISSOR_XY(x, y));
  243. cmd.cmd(SCISSOR_SIZE(w, h));
  244. }
  245. Polygon p(cmd);
  246. p.begin_fill();
  247. p.begin_loop();
  248. for (r.start();r.has_more();r.next()) {
  249. p(r.x * 16, r.y * 16);
  250. if (r.end_of_loop()) {
  251. p.end_loop();
  252. p.begin_loop();
  253. }
  254. }
  255. p.end_loop();
  256. p.end_fill();
  257. if (clip)
  258. cmd.cmd(RESTORE_CONTEXT());
  259. }
  260. void shadow(poly_reader_t r, uint8_t offset) {
  261. #if FTDI_API_LEVEL >= 810
  262. using namespace FTDI;
  263. cmd.cmd(VERTEX_TRANSLATE_X(offset * 16));
  264. cmd.cmd(VERTEX_TRANSLATE_Y(offset * 16));
  265. fill(r, false);
  266. cmd.cmd(VERTEX_TRANSLATE_X(0));
  267. cmd.cmd(VERTEX_TRANSLATE_Y(0));
  268. #endif
  269. }
  270. // Strokes a polygon with the current COLOR_RGB
  271. void stroke(poly_reader_t r) {
  272. using namespace FTDI;
  273. Polygon p(cmd);
  274. p.begin_stroke();
  275. p.begin_loop();
  276. for (r.start();r.has_more(); r.next()) {
  277. p(r.x * 16, r.y * 16);
  278. if (r.end_of_loop()) {
  279. p.end_loop();
  280. p.begin_loop();
  281. }
  282. }
  283. p.end_loop();
  284. p.end_stroke();
  285. }
  286. // Compute the bounds of a polygon
  287. void bounds(poly_reader_t r, int16_t &x, int16_t &y, int16_t &w, int16_t &h) {
  288. int16_t x_min = INT16_MAX;
  289. int16_t y_min = INT16_MAX;
  290. int16_t x_max = INT16_MIN;
  291. int16_t y_max = INT16_MIN;
  292. for (r.start(); r.has_more(); r.next()) {
  293. x_min = min(x_min, int16_t(r.x));
  294. x_max = max(x_max, int16_t(r.x));
  295. y_min = min(y_min, int16_t(r.y));
  296. y_max = max(y_max, int16_t(r.y));
  297. }
  298. x = x_min;
  299. y = y_min;
  300. w = x_max - x_min;
  301. h = y_max - y_min;
  302. }
  303. /**
  304. * Draw shaped buttons. Buttons are drawn out of a polygon which is
  305. * filled and stroked on top of a drop shadow. The button will
  306. * become "pushed" when touched.
  307. */
  308. void button_fill(const uint32_t color) {
  309. btn_fill_color = color;
  310. }
  311. void button_stroke(const uint32_t color, const uint8_t width) {
  312. btn_stroke_color = color;
  313. btn_stroke_width = width;
  314. }
  315. void button_shadow(const uint32_t color, const uint8_t depth) {
  316. btn_shadow_color = color;
  317. btn_shadow_depth = depth;
  318. }
  319. void button(const uint8_t tag, poly_reader_t r, uint8_t style = REGULAR) {
  320. using namespace FTDI;
  321. // Draw the shadow
  322. #if FTDI_API_LEVEL >= 810
  323. if (mode & BACKGROUND && style & SHADOW) {
  324. cmd.cmd(SAVE_CONTEXT());
  325. cmd.cmd(TAG(tag));
  326. cmd.cmd(VERTEX_TRANSLATE_X(btn_shadow_depth * 16));
  327. cmd.cmd(VERTEX_TRANSLATE_Y(btn_shadow_depth * 16));
  328. cmd.cmd(COLOR_RGB(btn_shadow_color));
  329. fill(r, false);
  330. cmd.cmd(RESTORE_CONTEXT());
  331. }
  332. #endif
  333. if (mode & FOREGROUND) {
  334. cmd.cmd(SAVE_CONTEXT());
  335. #if FTDI_API_LEVEL >= 810
  336. if (EventLoop::get_pressed_tag() == tag) {
  337. // "Push" the button
  338. cmd.cmd(VERTEX_TRANSLATE_X(btn_shadow_depth * 16));
  339. cmd.cmd(VERTEX_TRANSLATE_Y(btn_shadow_depth * 16));
  340. }
  341. #endif
  342. // Draw the fill and stroke
  343. cmd.cmd(TAG(tag));
  344. if (style & FILL) {
  345. cmd.cmd(COLOR_RGB(btn_fill_color));
  346. fill(r, false);
  347. }
  348. if (style & STROKE) {
  349. cmd.cmd(COLOR_RGB(btn_stroke_color));
  350. cmd.cmd(LINE_WIDTH(btn_stroke_width));
  351. stroke(r);
  352. }
  353. cmd.cmd(RESTORE_CONTEXT());
  354. }
  355. }
  356. void color(const uint32_t color) {
  357. cmd.cmd(FTDI::COLOR_RGB(color));
  358. }
  359. };
  360. typedef GenericPolyUI<> PolyUI;