decorations.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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) 2012-2018 K. Lange
  5. *
  6. * Client-side Window Decoration library
  7. */
  8. #include <stdint.h>
  9. #include <math.h>
  10. #include <dlfcn.h>
  11. #include <toaru/graphics.h>
  12. #include <toaru/yutani.h>
  13. #include <toaru/decorations.h>
  14. #include <toaru/sdf.h>
  15. #include <toaru/menu.h>
  16. #define TEXT_OFFSET_X 10
  17. #define TEXT_OFFSET_Y 3
  18. #define BORDERCOLOR rgb(59,59,59)
  19. #define BORDERCOLOR_INACTIVE rgb(30,30,30)
  20. #define TEXTCOLOR rgb(230,230,230)
  21. #define TEXTCOLOR_INACTIVE rgb(140,140,140)
  22. void (*decor_render_decorations)(yutani_window_t *, gfx_context_t *, char *, int) = NULL;
  23. int (*decor_check_button_press)(yutani_window_t *, int x, int y) = NULL;
  24. int (*decor_get_bounds)(yutani_window_t *, struct decor_bounds *) = NULL;
  25. static void (*callback_close)(yutani_window_t *) = NULL;
  26. static void (*callback_resize)(yutani_window_t *) = NULL;
  27. static void (*callback_maximize)(yutani_window_t *) = NULL;
  28. static int close_enough(struct yutani_msg_window_mouse_event * me) {
  29. return (me->command == YUTANI_MOUSE_EVENT_RAISE &&
  30. sqrt(pow(me->new_x - me->old_x, 2.0) + pow(me->new_y - me->old_y, 2.0)) < 10.0);
  31. }
  32. static void render_decorations_simple(yutani_window_t * window, gfx_context_t * ctx, char * title, int decors_active) {
  33. uint32_t color = BORDERCOLOR;
  34. if (decors_active == DECOR_INACTIVE) {
  35. color = BORDERCOLOR_INACTIVE;
  36. }
  37. for (int i = 0; i < (int)window->height; ++i) {
  38. GFX(ctx, 0, i) = color;
  39. GFX(ctx, window->width - 1, i) = color;
  40. }
  41. for (int i = 1; i < (int)24; ++i) {
  42. for (int j = 1; j < (int)window->width - 1; ++j) {
  43. GFX(ctx, j, i) = color;
  44. }
  45. }
  46. if (decors_active == DECOR_INACTIVE) {
  47. draw_sdf_string(ctx, TEXT_OFFSET_X, TEXT_OFFSET_Y, title, 14, TEXTCOLOR_INACTIVE, SDF_FONT_THIN);
  48. draw_sdf_string(ctx, window->width - 20, TEXT_OFFSET_Y, "x", 14, TEXTCOLOR_INACTIVE, SDF_FONT_THIN);
  49. } else {
  50. draw_sdf_string(ctx, TEXT_OFFSET_X, TEXT_OFFSET_Y, title, 14, TEXTCOLOR, SDF_FONT_THIN);
  51. draw_sdf_string(ctx, window->width - 20, TEXT_OFFSET_Y, "x", 14, TEXTCOLOR, SDF_FONT_THIN);
  52. }
  53. for (uint32_t i = 0; i < window->width; ++i) {
  54. GFX(ctx, i, 0) = color;
  55. GFX(ctx, i, 24 - 1) = color;
  56. GFX(ctx, i, window->height - 1) = color;
  57. }
  58. }
  59. static int check_button_press_simple(yutani_window_t * window, int x, int y) {
  60. if (x >= (int)window->width - 20 && x <= (int)window->width - 2 && y >= 2) {
  61. return DECOR_CLOSE;
  62. }
  63. return 0;
  64. }
  65. static int get_bounds_simple(yutani_window_t * window, struct decor_bounds * bounds) {
  66. /* Does not change with window state */
  67. bounds->top_height = 24;
  68. bounds->bottom_height = 1;
  69. bounds->left_width = 1;
  70. bounds->right_width = 1;
  71. bounds->width = bounds->left_width + bounds->right_width;
  72. bounds->height = bounds->top_height + bounds->bottom_height;
  73. return 0;
  74. }
  75. static void initialize_simple() {
  76. decor_render_decorations = render_decorations_simple;
  77. decor_check_button_press = check_button_press_simple;
  78. decor_get_bounds = get_bounds_simple;
  79. }
  80. void render_decorations(yutani_window_t * window, gfx_context_t * ctx, char * title) {
  81. if (!window) return;
  82. window->decorator_flags |= DECOR_FLAG_DECORATED;
  83. if (window->focused || !hashmap_is_empty(menu_get_windows_hash())) {
  84. decor_render_decorations(window, ctx, title, DECOR_ACTIVE);
  85. } else {
  86. decor_render_decorations(window, ctx, title, DECOR_INACTIVE);
  87. }
  88. }
  89. void render_decorations_inactive(yutani_window_t * window, gfx_context_t * ctx, char * title) {
  90. if (!window) return;
  91. window->decorator_flags |= DECOR_FLAG_DECORATED;
  92. decor_render_decorations(window, ctx, title, DECOR_INACTIVE);
  93. }
  94. static void _decor_maximize(yutani_t * yctx, yutani_window_t * window) {
  95. if (callback_maximize) {
  96. callback_maximize(window);
  97. } else {
  98. yutani_special_request(yctx, window, YUTANI_SPECIAL_REQUEST_MAXIMIZE);
  99. }
  100. }
  101. static yutani_window_t * _decor_menu_owner_window = NULL;
  102. static struct MenuList * _decor_menu = NULL;
  103. static void _decor_start_move(struct MenuEntry * self) {
  104. if (!_decor_menu_owner_window)
  105. return;
  106. yutani_focus_window(_decor_menu_owner_window->ctx, _decor_menu_owner_window->wid);
  107. yutani_window_drag_start(_decor_menu_owner_window->ctx, _decor_menu_owner_window);
  108. }
  109. static void _decor_start_maximize(struct MenuEntry * self) {
  110. if (!_decor_menu_owner_window)
  111. return;
  112. _decor_maximize(_decor_menu_owner_window->ctx, _decor_menu_owner_window);
  113. yutani_focus_window(_decor_menu_owner_window->ctx, _decor_menu_owner_window->wid);
  114. }
  115. static void _decor_close(struct MenuEntry * self) {
  116. if (!_decor_menu_owner_window)
  117. return;
  118. yutani_special_request(_decor_menu_owner_window->ctx, _decor_menu_owner_window, YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE);
  119. }
  120. yutani_window_t * decor_show_default_menu(yutani_window_t * window, int x, int y) {
  121. if (_decor_menu->window) return NULL;
  122. _decor_menu_owner_window = window;
  123. menu_show(_decor_menu, window->ctx);
  124. if (x + _decor_menu->window->width > window->ctx->display_width) {
  125. yutani_window_move(window->ctx, _decor_menu->window, x - _decor_menu->window->width, y);
  126. } else {
  127. yutani_window_move(window->ctx, _decor_menu->window, x, y);
  128. }
  129. return _decor_menu->window;
  130. }
  131. void init_decorations() {
  132. char * tmp = getenv("WM_THEME");
  133. char * theme = tmp ? strdup(tmp) : NULL;
  134. _decor_menu = menu_create();
  135. menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Maximize", _decor_start_maximize));
  136. menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Move", _decor_start_move));
  137. menu_insert(_decor_menu, menu_create_separator());
  138. menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Close", _decor_close));
  139. if (!theme || !strcmp(theme, "simple")) {
  140. initialize_simple();
  141. } else {
  142. char * options = strchr(theme,',');
  143. if (options) {
  144. *options = '\0';
  145. options++;
  146. }
  147. char lib_name[100];
  148. sprintf(lib_name, "libtoaru_decor-%s.so", theme);
  149. void * theme_lib = dlopen(lib_name, 0);
  150. if (!theme_lib) {
  151. goto _theme_error;
  152. }
  153. void (*theme_init)(char *) = dlsym(theme_lib, "decor_init");
  154. if (!theme_init) {
  155. goto _theme_error;
  156. }
  157. theme_init(options);
  158. return;
  159. _theme_error:
  160. fprintf(stderr, "decorations: could not load theme `%s`: %s\n", theme, dlerror());
  161. initialize_simple();
  162. }
  163. }
  164. void decor_set_close_callback(void (*callback)(yutani_window_t *)) {
  165. callback_close = callback;
  166. }
  167. void decor_set_resize_callback(void (*callback)(yutani_window_t *)) {
  168. callback_resize = callback;
  169. }
  170. void decor_set_maximize_callback(void (*callback)(yutani_window_t *)) {
  171. callback_maximize = callback;
  172. }
  173. static int within_decors(yutani_window_t * window, int x, int y) {
  174. struct decor_bounds bounds;
  175. decor_get_bounds(window, &bounds);
  176. if ((x <= (int)bounds.left_width || x >= (int)window->width - (int)bounds.right_width) && (x > 0 && x < (int)window->width)) return 1;
  177. if ((y <= (int)bounds.top_height || y >= (int)window->height - (int)bounds.bottom_height) && (y > 0 && y < (int)window->height)) return 1;
  178. return 0;
  179. }
  180. #define LEFT_SIDE (me->new_x <= (int)bounds.left_width)
  181. #define RIGHT_SIDE (me->new_x >= (int)window->width - (int)bounds.right_width)
  182. #define TOP_SIDE (me->new_y <= (int)bounds.top_height)
  183. #define BOTTOM_SIDE (me->new_y >= (int)window->height - (int)bounds.bottom_height)
  184. static yutani_scale_direction_t check_resize_direction(struct yutani_msg_window_mouse_event * me, yutani_window_t * window) {
  185. struct decor_bounds bounds;
  186. decor_get_bounds(window, &bounds);
  187. yutani_scale_direction_t resize_direction = SCALE_NONE;
  188. if (LEFT_SIDE && !TOP_SIDE && !BOTTOM_SIDE) {
  189. resize_direction = SCALE_LEFT;
  190. } else if (RIGHT_SIDE && !TOP_SIDE && !BOTTOM_SIDE) {
  191. resize_direction = SCALE_RIGHT;
  192. } else if (BOTTOM_SIDE && !LEFT_SIDE && !RIGHT_SIDE) {
  193. resize_direction = SCALE_DOWN;
  194. } else if (BOTTOM_SIDE && LEFT_SIDE) {
  195. resize_direction = SCALE_DOWN_LEFT;
  196. } else if (BOTTOM_SIDE && RIGHT_SIDE) {
  197. resize_direction = SCALE_DOWN_RIGHT;
  198. } else if (TOP_SIDE && LEFT_SIDE) {
  199. resize_direction = SCALE_UP_LEFT;
  200. } else if (TOP_SIDE && RIGHT_SIDE) {
  201. resize_direction = SCALE_UP_RIGHT;
  202. } else if (TOP_SIDE && (me->new_y < 5)) {
  203. resize_direction = SCALE_UP;
  204. }
  205. return resize_direction;
  206. }
  207. static yutani_scale_direction_t old_resize_direction = SCALE_NONE;
  208. int decor_handle_event(yutani_t * yctx, yutani_msg_t * m) {
  209. if (m) {
  210. switch (m->type) {
  211. case YUTANI_MSG_WINDOW_MOUSE_EVENT:
  212. {
  213. struct yutani_msg_window_mouse_event * me = (void*)m->data;
  214. yutani_window_t * window = hashmap_get(yctx->windows, (void*)me->wid);
  215. struct decor_bounds bounds;
  216. decor_get_bounds(window, &bounds);
  217. if (!window) return 0;
  218. if (!(window->decorator_flags & DECOR_FLAG_DECORATED)) return 0;
  219. if (within_decors(window, me->new_x, me->new_y)) {
  220. int button = decor_check_button_press(window, me->new_x, me->new_y);
  221. if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
  222. if (!button) {
  223. /* Resize edges */
  224. yutani_scale_direction_t resize_direction = check_resize_direction(me, window);
  225. if (resize_direction != SCALE_NONE) {
  226. yutani_window_resize_start(yctx, window, resize_direction);
  227. }
  228. if (me->new_y < (int)bounds.top_height && resize_direction == SCALE_NONE) {
  229. yutani_window_drag_start(yctx, window);
  230. }
  231. return DECOR_OTHER;
  232. }
  233. }
  234. if (!button && (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT)) {
  235. return DECOR_RIGHT;
  236. }
  237. if (me->command == YUTANI_MOUSE_EVENT_MOVE) {
  238. if (!button) {
  239. /* Resize edges */
  240. yutani_scale_direction_t resize_direction = check_resize_direction(me, window);
  241. if (resize_direction != old_resize_direction) {
  242. if (resize_direction == SCALE_NONE) {
  243. yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);
  244. } else {
  245. switch (resize_direction) {
  246. case SCALE_UP:
  247. case SCALE_DOWN:
  248. yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_VERTICAL);
  249. break;
  250. case SCALE_LEFT:
  251. case SCALE_RIGHT:
  252. yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL);
  253. break;
  254. case SCALE_DOWN_RIGHT:
  255. case SCALE_UP_LEFT:
  256. yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN);
  257. break;
  258. case SCALE_DOWN_LEFT:
  259. case SCALE_UP_RIGHT:
  260. yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP);
  261. break;
  262. case SCALE_AUTO:
  263. case SCALE_NONE:
  264. break;
  265. }
  266. }
  267. old_resize_direction = resize_direction;
  268. }
  269. }
  270. }
  271. if (me->command == YUTANI_MOUSE_EVENT_CLICK || close_enough(me)) {
  272. /* Determine if we clicked on a button */
  273. switch (button) {
  274. case DECOR_CLOSE:
  275. if (callback_close) callback_close(window);
  276. break;
  277. case DECOR_RESIZE:
  278. if (callback_resize) callback_resize(window);
  279. break;
  280. case DECOR_MAXIMIZE:
  281. _decor_maximize(yctx, window);
  282. break;
  283. default:
  284. break;
  285. }
  286. return button;
  287. }
  288. } else {
  289. if (old_resize_direction != SCALE_NONE) {
  290. yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);
  291. old_resize_direction = 0;
  292. }
  293. }
  294. }
  295. break;
  296. }
  297. }
  298. return 0;
  299. }