tutorial.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2019 K. Lange
  5. *
  6. * tutorial - A recreation of the original wizard.py, explaining
  7. * the functionality of ToaruOS and how to use the WM.
  8. */
  9. #include <toaru/yutani.h>
  10. #include <toaru/graphics.h>
  11. #include <toaru/decorations.h>
  12. #include <toaru/sdf.h>
  13. #include <toaru/menu.h>
  14. #include <toaru/button.h>
  15. #include <sys/utsname.h>
  16. #define BUTTON_HEIGHT 28
  17. #define BUTTON_WIDTH 86
  18. #define BUTTON_PADDING 14
  19. static yutani_t * yctx;
  20. static yutani_window_t * window = NULL;
  21. static gfx_context_t * ctx = NULL;
  22. static yutani_window_t * background = NULL;
  23. static gfx_context_t * background_ctx = NULL;
  24. static int32_t width = 640;
  25. static int32_t height = 480;
  26. static char * title_str;
  27. static char * body_text[20] = {NULL};
  28. static sprite_t * icon = NULL;
  29. static sprite_t terminal;
  30. static sprite_t folder;
  31. static sprite_t package;
  32. static sprite_t logo;
  33. static sprite_t mouse_drag;
  34. static int page = 0;
  35. static int center(int x, int width) {
  36. return (width - x) / 2;
  37. }
  38. static void draw_string(int y, const char * string, int font, uint32_t color, int size) {
  39. struct decor_bounds bounds;
  40. decor_get_bounds(window, &bounds);
  41. int text_width = draw_sdf_string_width(string, size, font);
  42. draw_sdf_string(ctx, center(text_width, width), bounds.top_height + 30 + y, string, size, color, font);
  43. }
  44. struct TTKButton _next_button = {0};
  45. struct TTKButton _prev_button = {0};
  46. static int _prev_enabled = 0;
  47. static void redraw(void) {
  48. struct decor_bounds bounds;
  49. decor_get_bounds(window, &bounds);
  50. draw_fill(ctx, rgb(204,204,204));
  51. int offset = 0;
  52. if (icon) {
  53. offset = icon->height + 20;
  54. draw_sprite(ctx, icon, center(icon->width, width), bounds.top_height + 15);
  55. }
  56. for (char ** copy_str = body_text; *copy_str; ++copy_str) {
  57. if (**copy_str == '-') {
  58. offset += 10;
  59. } else if (**copy_str == '%') {
  60. draw_string(offset, *copy_str+1, SDF_FONT_THIN, rgb(0,0,255), 16);
  61. offset += 20;
  62. } else if (**copy_str == '#') {
  63. draw_string(offset, *copy_str+1, SDF_FONT_BOLD, rgb(0,0,0), 23);
  64. offset += 20;
  65. } else {
  66. draw_string(offset, *copy_str, SDF_FONT_THIN, rgb(0,0,0), 16);
  67. offset += 20;
  68. }
  69. }
  70. ttk_button_draw(ctx, &_next_button);
  71. if (!_prev_enabled) {
  72. int _tmp = _prev_button.hilight;
  73. _prev_button.hilight = (1 << 8);
  74. ttk_button_draw(ctx, &_prev_button);
  75. _prev_button.hilight = _tmp;
  76. } else {
  77. ttk_button_draw(ctx, &_prev_button);
  78. }
  79. render_decorations(window, ctx, title_str);
  80. flip(ctx);
  81. yutani_flip(yctx, window);
  82. }
  83. static void reset_background(void) {
  84. draw_fill(background_ctx, rgba(0,0,0,200));
  85. }
  86. static void invert_background_alpha(void) {
  87. for (unsigned int y = 0; y < background->height; ++y) {
  88. for (unsigned int x = 0; x < background->width; ++x) {
  89. uint32_t c = GFX(background_ctx, x, y);
  90. int r = _RED(c);
  91. int g = _GRE(c);
  92. int b = _BLU(c);
  93. int a = _ALP(c);
  94. a = 255 - a;
  95. GFX(background_ctx, x, y) = rgba(r,g,b,a);
  96. }
  97. }
  98. }
  99. static void circle(int x, int y, int r) {
  100. draw_fill(background_ctx, rgba(0,0,0,255-200));
  101. draw_rounded_rectangle(background_ctx, x - r, y - r, r * 2, r * 2, r, rgb(0,0,0));
  102. invert_background_alpha();
  103. }
  104. static void load_page(int page) {
  105. int i = 0;
  106. _prev_enabled = 1;
  107. _next_button.title = "Next";
  108. reset_background();
  109. switch (page) {
  110. case 0:
  111. _prev_enabled = 0;
  112. title_str = "Welcome to ToaruOS!";
  113. icon = &logo;
  114. body_text[i++] = "#Welcome to ToaruOS!";
  115. body_text[i++] = "";
  116. body_text[i++] = "This tutorial will guide you through the features of the operating";
  117. body_text[i++] = "system, as well as give you a feel for the UI and design principles.";
  118. body_text[i++] = "";
  119. body_text[i++] = "When you're ready to continue, press \"Next\".";
  120. body_text[i++] = "";
  121. body_text[i++] = "%https://github.com/klange/toaruos - https://toaruos.org";
  122. body_text[i++] = "";
  123. body_text[i++] = "ToaruOS is free software, released under the terms of the";
  124. body_text[i++] = "NCSA/University of Illinois license.";
  125. body_text[i++] = NULL;
  126. break;
  127. case 1:
  128. icon = &logo;
  129. body_text[i++] = "ToaruOS is a hobby project. The entire contents of this Live CD";
  130. body_text[i++] = "were written by the ToaruOS development team over the course of";
  131. body_text[i++] = "many years, but that development team is very small. Some features";
  132. body_text[i++] = "may be missing, incomplete, or unstable. Contributions in the form";
  133. body_text[i++] = "of bug-fixes and new software are welcome. You can join our community";
  134. body_text[i++] = "through IRC by joining the #toaruos channel on Freenode.";
  135. body_text[i++] = NULL;
  136. break;
  137. case 2:
  138. icon = &folder;
  139. circle(70, 90, 60);
  140. body_text[i++] = "You can explore the file system using the File Browser.";
  141. body_text[i++] = "Application shortcuts on the desktop, as well as files in the file browser";
  142. body_text[i++] = "are opened with a double click. You can also find more applications in";
  143. body_text[i++] = "the Applications menu in the upper left.";
  144. body_text[i++] = NULL;
  145. break;
  146. case 3:
  147. icon = &terminal;
  148. circle(70, 170, 60);
  149. body_text[i++] = "ToaruOS aims to provide a Unix-like environment. You can find";
  150. body_text[i++] = "familiar command-line tools by opening a terminal. ToaruOS's";
  151. body_text[i++] = "shell provides command history, syntax highlighting, and tab";
  152. body_text[i++] = "completion. There is also a growing suite of Unix utilities";
  153. body_text[i++] = "and a featureful text editor (bim).";
  154. body_text[i++] = NULL;
  155. break;
  156. case 4:
  157. icon = &package;
  158. circle(70, 250, 60);
  159. body_text[i++] = "Many third-party software packages have been ported to ToaruOS";
  160. body_text[i++] = "and are available from our package repositories. You can use the";
  161. body_text[i++] = "Package Manager to install GCC, Python, Bochs, Quake, and more.";
  162. body_text[i++] = "";
  163. body_text[i++] = "The Package Manager will prompt you to authenticate. The default";
  164. body_text[i++] = "user is 'local' with the password 'local'. There is also a 'root'";
  165. body_text[i++] = "user with the password 'toor'.";
  166. body_text[i++] = NULL;
  167. break;
  168. case 5:
  169. icon = &mouse_drag;
  170. body_text[i++] = "With ToaruOS's window manager, you can drag most windows by";
  171. body_text[i++] = "holding Alt, or by using the title bar. You can also resize";
  172. body_text[i++] = "windows by dragging from their edges or using Alt + Middle Click.";
  173. body_text[i++] = "";
  174. body_text[i++] = "If you are running ToaruOS in VirtualBox, be sure to select a Host";
  175. body_text[i++] = "key configuration that does not conflict with these key bindings.";
  176. body_text[i++] = NULL;
  177. break;
  178. case 6:
  179. icon = NULL;
  180. _next_button.title = "Exit";
  181. body_text[i++] = "#That's it!";
  182. body_text[i++] = "";
  183. body_text[i++] = "The tutorial is over.";
  184. body_text[i++] = "";
  185. body_text[i++] = "Press \"Exit\" to close this window and start exploring ToaruOS.";
  186. body_text[i++] = NULL;
  187. break;
  188. default:
  189. exit(0);
  190. break;
  191. }
  192. flip(background_ctx);
  193. yutani_flip(yctx, background);
  194. }
  195. int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) {
  196. if (me->new_y >= button->y && me->new_y < button->y + button->height) {
  197. if (me->new_x >= button->x && me->new_x < button->x + button->width) {
  198. return 1;
  199. }
  200. }
  201. return 0;
  202. }
  203. void setup_buttons(void) {
  204. struct decor_bounds bounds;
  205. decor_get_bounds(window, &bounds);
  206. _next_button.title = "Next";
  207. _next_button.width = BUTTON_WIDTH;
  208. _next_button.height = BUTTON_HEIGHT;
  209. _next_button.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING;
  210. _next_button.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;
  211. _prev_button.title = "Back";
  212. _prev_button.width = BUTTON_WIDTH;
  213. _prev_button.height = BUTTON_HEIGHT;
  214. _prev_button.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 2;
  215. _prev_button.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;
  216. }
  217. void resize_finish(int w, int h) {
  218. yutani_window_resize_accept(yctx, window, w, h);
  219. reinit_graphics_yutani(ctx, window);
  220. width = w;
  221. height = h;
  222. setup_buttons();
  223. redraw();
  224. yutani_window_resize_done(yctx, window);
  225. }
  226. void resize_finish_bg(int w, int h) {
  227. yutani_window_resize_accept(yctx, background, w, h);
  228. reinit_graphics_yutani(background_ctx, background);
  229. load_page(page);
  230. yutani_window_resize_done(yctx, background);
  231. }
  232. void set_hilight(struct TTKButton * button, int hilight) {
  233. if (!button && (_next_button.hilight || _prev_button.hilight)) {
  234. _next_button.hilight = 0;
  235. _prev_button.hilight = 0;
  236. redraw();
  237. } else if (button && (button->hilight != hilight)) {
  238. _next_button.hilight = 0;
  239. _prev_button.hilight = 0;
  240. button->hilight = hilight;
  241. redraw();
  242. }
  243. }
  244. int main(int argc, char * argv[]) {
  245. int req_center_x, req_center_y;
  246. yctx = yutani_init();
  247. if (!yctx) {
  248. fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]);
  249. return 1;
  250. }
  251. init_decorations();
  252. background = yutani_window_create_flags(yctx, yctx->display_width, yctx->display_height,
  253. YUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_DISALLOW_DRAG |
  254. YUTANI_WINDOW_FLAG_ALT_ANIMATION | YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS);
  255. yutani_window_move(yctx, background, 0, 0);
  256. yutani_window_update_shape(yctx, background, 2);
  257. background_ctx = init_graphics_yutani_double_buffer(background);
  258. reset_background();
  259. flip(background_ctx);
  260. yutani_flip(yctx, background);
  261. struct decor_bounds bounds;
  262. decor_get_bounds(NULL, &bounds);
  263. window = yutani_window_create(yctx, width + bounds.width, height + bounds.height);
  264. /* Load icons */
  265. load_sprite(&logo, "/usr/share/logo_login.bmp");
  266. logo.alpha = ALPHA_EMBEDDED;
  267. load_sprite(&terminal, "/usr/share/icons/48/utilities-terminal.bmp");
  268. terminal.alpha = ALPHA_EMBEDDED;
  269. load_sprite(&folder, "/usr/share/icons/48/folder.bmp");
  270. folder.alpha = ALPHA_EMBEDDED;
  271. load_sprite(&package, "/usr/share/icons/48/package.bmp");
  272. package.alpha = ALPHA_EMBEDDED;
  273. load_sprite(&mouse_drag, "/usr/share/cursor/drag.bmp");
  274. mouse_drag.alpha = ALPHA_EMBEDDED;
  275. load_page(0);
  276. req_center_x = yctx->display_width / 2;
  277. req_center_y = yctx->display_height / 2;
  278. yutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);
  279. yutani_window_advertise_icon(yctx, window, title_str, "star");
  280. ctx = init_graphics_yutani_double_buffer(window);
  281. setup_buttons();
  282. redraw();
  283. struct TTKButton * _down_button = NULL;
  284. int playing = 1;
  285. int status = 0;
  286. while (playing) {
  287. yutani_msg_t * m = yutani_poll(yctx);
  288. while (m) {
  289. if (menu_process_event(yctx, m)) {
  290. redraw();
  291. }
  292. switch (m->type) {
  293. case YUTANI_MSG_KEY_EVENT:
  294. {
  295. struct yutani_msg_key_event * ke = (void*)m->data;
  296. if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == '\n') {
  297. page++;
  298. load_page(page);
  299. redraw();
  300. } else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) {
  301. playing = 0;
  302. status = 2;
  303. }
  304. }
  305. break;
  306. case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
  307. {
  308. struct yutani_msg_window_focus_change * wf = (void*)m->data;
  309. yutani_window_t * win = hashmap_get(yctx->windows, (void*)wf->wid);
  310. if (wf->wid == background->wid) {
  311. yutani_focus_window(yctx, window->wid);
  312. } else if (win) {
  313. win->focused = wf->focused;
  314. redraw();
  315. }
  316. }
  317. break;
  318. case YUTANI_MSG_WELCOME:
  319. yutani_window_resize_offer(yctx, background, yctx->display_width, yctx->display_height);
  320. req_center_x = yctx->display_width / 2;
  321. req_center_y = yctx->display_height / 2;
  322. yutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);
  323. break;
  324. case YUTANI_MSG_RESIZE_OFFER:
  325. {
  326. struct yutani_msg_window_resize * wr = (void*)m->data;
  327. if (wr->wid == window->wid) {
  328. resize_finish(wr->width, wr->height);
  329. } else if (wr->wid == background->wid) {
  330. resize_finish_bg(wr->width, wr->height);
  331. }
  332. }
  333. break;
  334. case YUTANI_MSG_WINDOW_MOUSE_EVENT:
  335. {
  336. struct yutani_msg_window_mouse_event * me = (void*)m->data;
  337. if (me->wid == window->wid) {
  338. int result = decor_handle_event(yctx, m);
  339. switch (result) {
  340. case DECOR_CLOSE:
  341. playing = 0;
  342. status = 2;
  343. break;
  344. case DECOR_RIGHT:
  345. /* right click in decoration, show appropriate menu */
  346. decor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);
  347. break;
  348. default:
  349. /* Other actions */
  350. break;
  351. }
  352. struct decor_bounds bounds;
  353. decor_get_bounds(window, &bounds);
  354. if (me->new_y > bounds.top_height) {
  355. if (me->command == YUTANI_MOUSE_EVENT_DOWN) {
  356. if (in_button(&_next_button, me)) {
  357. set_hilight(&_next_button, 2);
  358. _down_button = &_next_button;
  359. } else if (in_button(&_prev_button, me)) {
  360. set_hilight(&_prev_button, 2);
  361. _down_button = &_prev_button;
  362. }
  363. } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {
  364. if (_down_button) {
  365. if (in_button(_down_button, me)) {
  366. if (_down_button == &_prev_button) {
  367. if (page > 0) {
  368. page--;
  369. load_page(page);
  370. }
  371. } else if (_down_button == &_next_button) {
  372. page++;
  373. load_page(page);
  374. }
  375. _down_button->hilight = 0;
  376. }
  377. }
  378. _down_button = NULL;
  379. }
  380. if (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
  381. if (in_button(&_next_button, me)) {
  382. set_hilight(&_next_button, 1);
  383. } else if (in_button(&_prev_button, me)) {
  384. set_hilight(&_prev_button, 1);
  385. } else {
  386. set_hilight(NULL,0);
  387. }
  388. } else if (_down_button) {
  389. if (in_button(_down_button, me)) {
  390. set_hilight(_down_button, 2);
  391. } else {
  392. set_hilight(NULL, 0);
  393. }
  394. }
  395. }
  396. }
  397. }
  398. break;
  399. case YUTANI_MSG_WINDOW_CLOSE:
  400. case YUTANI_MSG_SESSION_END:
  401. playing = 0;
  402. status = 2;
  403. break;
  404. default:
  405. break;
  406. }
  407. free(m);
  408. m = yutani_poll_async(yctx);
  409. }
  410. }
  411. yutani_close(yctx, window);
  412. return status;
  413. }