ui.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. #include <stdio.h>
  2. extern "C" {
  3. #include "main.h"
  4. #include "cmsis_os.h"
  5. #include "u8g2_support.h"
  6. #include "u8g2/u8g2.h"
  7. }
  8. #include "ui.h"
  9. #include "events.h"
  10. // function draw basic layout -- single bmp
  11. void draw_bitmap(const char* bitmap, u8g2_t* u8g2, ScreenArea area) {
  12. if(bitmap == NULL) {
  13. printf("[basic layout] no content\n");
  14. u8g2_SetFont(u8g2, u8g2_font_6x10_mf);
  15. u8g2_SetDrawColor(u8g2, 1);
  16. u8g2_SetFontMode(u8g2, 1);
  17. u8g2_DrawStr(u8g2, 2, 12, "no content");
  18. } else {
  19. u8g2_SetDrawColor(u8g2, 1);
  20. u8g2_DrawXBM(u8g2, 0, 0, area.x + area.width, area.y + area.height, (unsigned char*)bitmap);
  21. }
  22. }
  23. void draw_text(const char* text, u8g2_t* u8g2, ScreenArea area) {
  24. // TODO proper cleanup statusbar
  25. u8g2_SetDrawColor(u8g2, 0);
  26. u8g2_DrawBox(u8g2, 0, 0, area.x + area.width, area.y + area.height);
  27. Block text_block = Block {
  28. width: area.width,
  29. height: area.height,
  30. margin_left: 0,
  31. margin_top: 0,
  32. padding_left: 3,
  33. padding_top: 7,
  34. background: 0,
  35. color: 1,
  36. font: (uint8_t*)u8g2_font_6x10_mf,
  37. };
  38. draw_block(u8g2, text, text_block, area.x, area.y);
  39. }
  40. // draw layout and switch between ui item by button and timer
  41. void LayoutComponent::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
  42. switch(event->type) {
  43. // get button event
  44. case EventTypeButton:
  45. if(event->value.button.state) {
  46. for(size_t i = 0; i < this->actions_size; i++) {
  47. FlipperComponent* next = NULL;
  48. switch(this->actions[i].action) {
  49. case LayoutActionUp:
  50. if(event->value.button.id == ButtonsUp) {
  51. next = this->actions[i].item;
  52. }
  53. break;
  54. case LayoutActionDown:
  55. if(event->value.button.id == ButtonsDown) {
  56. next = this->actions[i].item;
  57. }
  58. break;
  59. case LayoutActionLeft:
  60. if(event->value.button.id == ButtonsLeft) {
  61. next = this->actions[i].item;
  62. }
  63. break;
  64. case LayoutActionRight:
  65. if(event->value.button.id == ButtonsRight) {
  66. next = this->actions[i].item;
  67. }
  68. break;
  69. case LayoutActionOk:
  70. if(event->value.button.id == ButtonsOk) {
  71. next = this->actions[i].item;
  72. }
  73. break;
  74. case LayoutActionBack:
  75. if(event->value.button.id == ButtonsBack) {
  76. next = this->actions[i].item;
  77. }
  78. break;
  79. // stub action
  80. case LayoutActionUsbDisconnect:
  81. if(event->value.button.id == ButtonsLeft) {
  82. next = this->actions[i].item;
  83. }
  84. break;
  85. case LayoutActionUsbConnect:
  86. if(event->value.button.id == ButtonsRight) {
  87. next = this->actions[i].item;
  88. }
  89. break;
  90. default: break;
  91. }
  92. if(next) {
  93. printf("[layout view] go to next item\n");
  94. Event send_event;
  95. send_event.type = EventTypeUiNext;
  96. next->handle(
  97. &send_event,
  98. store,
  99. u8g2,
  100. area
  101. );
  102. }
  103. }
  104. }
  105. break;
  106. case EventTypeUsb: {
  107. printf("get usb event\n");
  108. FlipperComponent* next = NULL;
  109. if(event->value.usb == UsbEventConnect) {
  110. for(size_t i = 0; i < this->actions_size; i++) {
  111. if(this->actions[i].action == LayoutActionUsbConnect) {
  112. next = this->actions[i].item;
  113. }
  114. }
  115. }
  116. if(event->value.usb == UsbEventDisconnect) {
  117. for(size_t i = 0; i < this->actions_size; i++) {
  118. if(this->actions[i].action == LayoutActionUsbDisconnect) {
  119. next = this->actions[i].item;
  120. }
  121. }
  122. }
  123. if(next) {
  124. printf("[layout view] go to next item\n");
  125. Event send_event;
  126. send_event.type = EventTypeUiNext;
  127. next->handle(
  128. &send_event,
  129. store,
  130. u8g2,
  131. area
  132. );
  133. }
  134. } break;
  135. // start component from prev
  136. case EventTypeUiNext:
  137. printf("[layout view] start component %lX\n", (uint32_t)this);
  138. if(this->timeout > 0) {
  139. // TODO start timer
  140. }
  141. // set current item to self
  142. store->current_component = this;
  143. this->wait_time = 0;
  144. // render layout
  145. this->dirty = true;
  146. break;
  147. case EventTypeTick:
  148. this->wait_time += event->value.tick_delta;
  149. if(this->wait_time > this->timeout) {
  150. for(size_t i = 0; i < this->actions_size; i++) {
  151. if(this->actions[i].action == LayoutActionTimeout ||
  152. this->actions[i].action == LayoutActionEndOfCycle
  153. ) {
  154. if(this->actions[i].item != NULL) {
  155. printf("[layout view] go to next item\n");
  156. Event send_event;
  157. send_event.type = EventTypeUiNext;
  158. this->actions[i].item->handle(
  159. &send_event,
  160. store,
  161. u8g2,
  162. area
  163. );
  164. return;
  165. }
  166. }
  167. }
  168. }
  169. if(this->dirty) {
  170. this->draw_fn(this->data, u8g2, area);
  171. store->dirty_screen = true;
  172. this->dirty = false;
  173. }
  174. break;
  175. default: break;
  176. }
  177. }
  178. void BlinkerComponent::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
  179. switch(event->type) {
  180. // get button event
  181. case EventTypeButton:
  182. if(event->value.button.state && event->value.button.id == ButtonsBack) {
  183. if(this->prev && this->prev != this) {
  184. printf("[blinker view] go back\n");
  185. Event send_event;
  186. send_event.type = EventTypeUiNext;
  187. this->prev->handle(
  188. &send_event,
  189. store,
  190. u8g2,
  191. area
  192. );
  193. this->prev = NULL;
  194. store->led = ColorBlack;
  195. this->wait_time = 0;
  196. this->is_on = true;
  197. this->active = false;
  198. } else {
  199. printf("[blinker view] no back/loop\n");
  200. }
  201. }
  202. if(event->value.button.state && event->value.button.id != ButtonsBack) {
  203. this->active = false;
  204. }
  205. if(!event->value.button.state && event->value.button.id != ButtonsBack) {
  206. this->active = true;
  207. }
  208. break;
  209. // start component from prev
  210. case EventTypeUiNext:
  211. printf("[blinker view] start component %lX\n", (uint32_t)this);
  212. if(this->prev == NULL) {
  213. this->prev = store->current_component;
  214. }
  215. // set current item to self
  216. store->current_component = this;
  217. this->dirty = true;
  218. this->wait_time = 0;
  219. this->is_on = true;
  220. this->active = false;
  221. break;
  222. case EventTypeTick:
  223. if(this->active) {
  224. this->wait_time += event->value.tick_delta;
  225. if(this->is_on) {
  226. if(this->wait_time > this->config.on_time) {
  227. this->wait_time = 0;
  228. this->is_on = false;
  229. }
  230. } else {
  231. if(this->wait_time > this->config.off_time) {
  232. this->wait_time = 0;
  233. this->is_on = true;
  234. }
  235. }
  236. store->led = this->is_on ? this->config.on_color : this->config.off_color;
  237. } else {
  238. store->led = ColorBlack;
  239. this->wait_time = 0;
  240. this->is_on = true;
  241. }
  242. if(this->dirty) {
  243. this->draw_fn(this->data, u8g2, area);
  244. store->dirty_screen = true;
  245. this->dirty = false;
  246. }
  247. break;
  248. default: break;
  249. }
  250. }
  251. void BlinkerComponentOnBtn::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
  252. switch(event->type) {
  253. // get button event
  254. case EventTypeButton:
  255. if(event->value.button.state && event->value.button.id == ButtonsBack) {
  256. if(this->prev && this->prev != this) {
  257. printf("[blinker view] go back\n");
  258. Event send_event;
  259. send_event.type = EventTypeUiNext;
  260. this->prev->handle(
  261. &send_event,
  262. store,
  263. u8g2,
  264. area
  265. );
  266. this->prev = NULL;
  267. store->led = ColorBlack;
  268. this->wait_time = 0;
  269. this->is_on = true;
  270. this->active = false;
  271. } else {
  272. printf("[blinker view] no back/loop\n");
  273. }
  274. }
  275. if(event->value.button.state && event->value.button.id != ButtonsBack) {
  276. this->active = true;
  277. }
  278. if(!event->value.button.state && event->value.button.id != ButtonsBack) {
  279. this->active = false;
  280. }
  281. break;
  282. // start component from prev
  283. case EventTypeUiNext:
  284. printf("[blinker view] start component %lX\n", (uint32_t)this);
  285. if(this->prev == NULL) {
  286. this->prev = store->current_component;
  287. }
  288. // set current item to self
  289. store->current_component = this;
  290. this->dirty = true;
  291. this->wait_time = 0;
  292. this->is_on = true;
  293. this->active = false;
  294. break;
  295. case EventTypeTick:
  296. if(this->active) {
  297. this->wait_time += event->value.tick_delta;
  298. if(this->is_on) {
  299. if(this->wait_time > this->config.on_time) {
  300. this->wait_time = 0;
  301. this->is_on = false;
  302. }
  303. } else {
  304. if(this->wait_time > this->config.off_time) {
  305. this->wait_time = 0;
  306. this->is_on = true;
  307. }
  308. }
  309. store->led = this->is_on ? this->config.on_color : this->config.off_color;
  310. } else {
  311. store->led = ColorBlack;
  312. this->wait_time = 0;
  313. this->is_on = true;
  314. }
  315. if(this->dirty) {
  316. this->draw_fn(this->data, u8g2, area);
  317. store->dirty_screen = true;
  318. this->dirty = false;
  319. }
  320. break;
  321. default: break;
  322. }
  323. }
  324. #define MENU_DRAW_LINES 4
  325. Point draw_block(u8g2_t* u8g2, const char* text, Block layout, uint8_t x, uint8_t y) {
  326. u8g2_SetDrawColor(u8g2, layout.background);
  327. u8g2_DrawBox(u8g2,
  328. x + layout.margin_left,
  329. y + layout.margin_top,
  330. layout.width, layout.height
  331. );
  332. u8g2_SetDrawColor(u8g2, layout.color);
  333. u8g2_SetFont(u8g2, layout.font);
  334. if(text != NULL) {
  335. u8g2_DrawStr(u8g2,
  336. x + layout.margin_left + layout.padding_left,
  337. y + layout.margin_top + layout.padding_top,
  338. text
  339. );
  340. }
  341. return {
  342. x: x + layout.margin_left + layout.width,
  343. y: y + layout.margin_top + layout.height
  344. };
  345. }
  346. void draw_menu(MenuCtx* ctx, u8g2_t* u8g2, ScreenArea area) {
  347. // u8g2_ClearBuffer(u8g2);
  348. // clear area
  349. u8g2_SetDrawColor(u8g2, 0);
  350. u8g2_DrawBox(u8g2, area.x, area.y, area.width, area.height);
  351. u8g2_SetFontMode(u8g2, 1);
  352. uint8_t list_start = ctx->current - ctx->cursor;
  353. uint8_t list_size = ctx->size > MENU_DRAW_LINES ? MENU_DRAW_LINES : ctx->size;
  354. // draw header
  355. /*
  356. Point next = draw_block(u8g2, (const char*)data->name, Block {
  357. width: 128,
  358. height: 14,
  359. margin_left: 0,
  360. margin_top: 0,
  361. padding_left: 4,
  362. padding_top: 13,
  363. background: 1,
  364. color: 0,
  365. font: (uint8_t*)u8g2_font_helvB14_tf,
  366. }, area.x, area.y);
  367. */
  368. Point next = {area.x, area.y};
  369. for(size_t i = 0; i < list_size; i++) {
  370. next = draw_block(u8g2, (const char*)ctx->list[list_start + i].name, Block {
  371. width: 128,
  372. height: 15,
  373. margin_left: 0,
  374. margin_top: i == 0 ? 2 : 0,
  375. padding_left: 2,
  376. padding_top: 12,
  377. background: i == ctx->cursor ? 1 : 0,
  378. color: i == ctx->cursor ? 0 : 1,
  379. font: (uint8_t*)u8g2_font_7x14_tf,
  380. }, area.x, next.y);
  381. }
  382. // u8g2_font_7x14_tf
  383. // u8g2_font_profont12_tf smallerbut cute
  384. // u8g2_font_samim_12_t_all орочий
  385. }
  386. void MenuCtx::handle(MenuEvent event) {
  387. uint8_t menu_size = this->size > MENU_DRAW_LINES ? MENU_DRAW_LINES : this->size;
  388. switch(event) {
  389. case MenuEventDown: {
  390. if(this->current < (this->size - 1)) {
  391. this->current++;
  392. if(this->cursor < menu_size - 2 || this->current == this->size - 1) {
  393. this->cursor++;
  394. }
  395. } else {
  396. this->current = 0;
  397. this->cursor = 0;
  398. }
  399. } break;
  400. case MenuEventUp: {
  401. if(this->current > 0) {
  402. this->current--;
  403. if(this->cursor > 1 || this->current == 0) {
  404. this->cursor--;
  405. }
  406. } else {
  407. this->current = this->size - 1;
  408. this->cursor = menu_size - 1;
  409. }
  410. } break;
  411. }
  412. }
  413. void MenuCtx::reset() {
  414. this->current = 0;
  415. this->cursor = 0;
  416. }
  417. // draw numenu and handle navigation
  418. void MenuComponent::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
  419. switch(event->type) {
  420. // get button event
  421. case EventTypeButton: {
  422. if(event->value.button.id == ButtonsOk && event->value.button.state) {
  423. if(this->ctx.current < this->ctx.size) {
  424. FlipperComponent* next_item = (FlipperComponent*)this->ctx.list[this->ctx.current].item;
  425. if(next_item) {
  426. store->is_fullscreen = false;
  427. printf("[layout view] go to %d item\n", this->ctx.current);
  428. Event send_event;
  429. send_event.type = EventTypeUiNext;
  430. next_item->handle(
  431. &send_event,
  432. store,
  433. u8g2,
  434. area
  435. );
  436. } else {
  437. printf("[menu view] no item at %d\n", this->ctx.current);
  438. }
  439. }
  440. }
  441. if(event->value.button.id == ButtonsDown && event->value.button.state) {
  442. this->ctx.handle(MenuEventDown);
  443. this->dirty = true;
  444. }
  445. if(event->value.button.id == ButtonsUp && event->value.button.state) {
  446. this->ctx.handle(MenuEventUp);
  447. this->dirty = true;
  448. }
  449. // go back item
  450. if(event->value.button.id == ButtonsBack && event->value.button.state) {
  451. if(this->prev && this->prev != this) {
  452. store->is_fullscreen = false;
  453. printf("[menu view] go back\n");
  454. this->ctx.reset();
  455. Event send_event;
  456. send_event.type = EventTypeUiNext;
  457. this->prev->handle(
  458. &send_event,
  459. store,
  460. u8g2,
  461. area
  462. );
  463. this->prev = NULL;
  464. } else {
  465. printf("[menu view] no back/loop\n");
  466. }
  467. }
  468. } break;
  469. // start component from prev
  470. case EventTypeUiNext:
  471. printf("[menu view] start component %lX (size %d)\n", (uint32_t)this, this->ctx.size);
  472. // set prev item
  473. if(this->prev == NULL) {
  474. printf("[menu view] set prev element to %lX\n", (uint32_t)store->current_component);
  475. this->prev = store->current_component;
  476. }
  477. // set current item to self
  478. store->current_component = this;
  479. store->is_fullscreen = true;
  480. // render menu
  481. this->dirty = true;
  482. break;
  483. case EventTypeTick:
  484. if(this->dirty) {
  485. draw_menu(&this->ctx, u8g2, area);
  486. store->dirty_screen = true;
  487. this->dirty = false;
  488. }
  489. break;
  490. default: break;
  491. }
  492. }