changed rendering
[taskasaur.git] / menu.c
1
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include <string.h>
5 #include <stdbool.h>
6 #include <ncurses.h>
7
8 #include "headers/menu.h"
9 #include "headers/render.h"
10 #include "headers/utils.h"
11
12 #define MENU_PAD_TOP 2
13 #define MENU_PAD_BOTTOM 2
14 #define MENU_PAD_LEFT 2
15 #define MENU_PAD_RIGHT 1
16
17 #define MAX_CONTENTS_LENGTH 256
18
19 typedef struct MenuItem {
20     char* title;
21     char* description;
22     void* userdata;
23 } MenuItem;
24
25 typedef struct Menu {
26     char* menu_name;
27     MenuItem** menu_items;
28     int menu_length;
29     int selected_item;
30     int scroll_offset;
31     bool focused;
32     WINDOW* menu_win;
33     WINDOW* sub_win;
34     int max_height;
35     int max_width;
36     void* userdata;
37 } Menu;
38
39 int swap_item(Menu* menu, int src_index, int dest_index);
40
41 /* insert mode */
42 int menu_insert_mode(Menu* menu, int insert_index);
43
44 /* prob temp for now */
45 MenuItem* create_blank_menuitem(void);
46
47 /* rendering stuff */
48 int render_item(Menu* menu, int item_index, int start_y);
49 int item_height(MenuItem* menuitem);
50 int items_visible(Menu* menu, int offset);
51 int items_visible_rev(Menu* menu, int offset);
52
53 MenuItem*
54 create_menuitem(char* title)
55 {
56     MenuItem* new_menuitem;
57     TodoItem* new_userdata;
58
59     new_menuitem = malloc(sizeof(MenuItem));
60     new_menuitem->title = title;
61     new_menuitem->description = strdup(""); //TEMP FOR NOW
62
63     new_userdata = malloc(sizeof(TodoItem));
64     new_userdata->item_name = title;
65     new_userdata->description = strdup("");
66     new_userdata->due = strdup("");
67     new_userdata->subtask_list = malloc(0);
68     new_userdata->subtask_count = 0;
69     
70     new_menuitem->userdata = new_userdata;
71
72     return new_menuitem;
73 }
74
75 MenuItem*
76 create_blank_menuitem(void)
77 {
78     return create_menuitem(strdup(""));
79 }
80
81 Menu* 
82 create_menu(char* menu_name, MenuItem** item_list)
83 {
84     Menu* new_menu;
85
86     new_menu = malloc(sizeof(Menu));
87     new_menu->menu_name = menu_name;
88     new_menu->menu_items = item_list;
89     new_menu->menu_length = array_length(MenuItem*, item_list);
90     new_menu->selected_item = 0;
91     new_menu->scroll_offset = 0;
92     new_menu->focused = false;
93     set_menu_win(new_menu, stdscr);
94
95     return new_menu;
96 }
97
98 /* getters */
99 WINDOW*
100 get_menu_win(Menu* menu)
101 {
102     return menu->menu_win;
103 }
104
105 WINDOW*
106 get_menu_subwin(Menu* menu)
107 {
108     return menu->sub_win;
109 }
110
111 MenuItem*
112 get_menu_item(Menu* menu, int index)
113 {
114     if (index < 0 || index >= menu->menu_length) return NULL;
115
116     return menu->menu_items[index];
117 }
118
119 int
120 get_selected_item(Menu* menu)
121 {
122     return menu->selected_item;
123 }
124
125 int
126 get_menu_length(Menu* menu)
127 {
128     return menu->menu_length;
129 }
130
131 char*
132 get_menu_name(Menu* menu)
133 {
134     return menu->menu_name;
135 }
136
137 void*
138 get_menu_userdata(Menu* menu)
139 {
140     return menu->userdata;
141 }
142
143 char*
144 get_menuitem_title(MenuItem* menuitem)
145 {
146     return menuitem->title;
147 }
148
149 char*
150 get_menuitem_descrip(MenuItem* menuitem)
151 {
152     return menuitem->description;
153 }
154
155 void*
156 get_menuitem_userdata(MenuItem* menuitem)
157 {
158     return menuitem->userdata;
159 }
160
161
162 /* setters */
163 int
164 set_menu_win(Menu* menu, WINDOW* win)
165 {
166     int height, width;
167
168     menu->menu_win = win;
169     getmaxyx(menu->menu_win, height, width);
170
171     /* create a subwin (also prob free old subwin?) */
172     menu->max_height = height-MENU_PAD_TOP-MENU_PAD_BOTTOM;
173     menu->max_width = width-MENU_PAD_LEFT-MENU_PAD_RIGHT;
174     menu->sub_win = derwin(
175             menu->menu_win, 
176             menu->max_height,
177             menu->max_width,
178             MENU_PAD_TOP, 
179             MENU_PAD_LEFT
180     );
181
182     return 0;
183 }
184
185 int
186 set_selected_item(Menu* menu, int selected_item)
187 {
188     menu->selected_item = selected_item;
189     return 0;
190 }
191
192 int
193 set_menu_focus(Menu* menu, bool focus)
194 {
195     menu->focused = focus;
196     return 0;
197 }
198
199 int
200 set_menu_userdata(Menu* menu, void* userdata)
201 {
202     menu->userdata = userdata;
203     return 0;
204 }
205
206 int
207 set_menuitem_descrip(MenuItem* menuitem, char* descrip)
208 {
209     menuitem->description = descrip;
210     return 0;
211 }
212
213 int
214 set_menuitem_userdata(MenuItem* menuitem, void* userdata)
215 {
216     menuitem->userdata = userdata;
217     return 0;
218 }
219
220
221 int
222 swap_item(Menu* menu, int src_index, int dest_index)
223 {
224     ar_swap_item((void**)menu->menu_items, src_index, dest_index);
225
226     return 0;
227 }
228
229 int
230 delete_item(Menu* menu, int index)
231 {
232     if (index < 0 || index > menu->menu_length-1) return -1;
233
234     for (int i = index; i <= menu->menu_length-1; i++) {
235         menu->menu_items[i] = menu->menu_items[i+1];
236     }
237
238     menu->menu_items = realloc(menu->menu_items, menu->menu_length*sizeof(MenuItem*)); 
239     menu->menu_items[menu->menu_length-1] = 0; // preserve null at end
240
241     menu->menu_length -= 1;
242
243     /* also move the current selected position if it's last */
244     if (menu->selected_item > menu->menu_length-1) {
245         menu->selected_item = menu->menu_length-1;
246     }
247
248     return 0;
249 }
250
251 int 
252 insert_item(Menu* menu, MenuItem* menuitem, int index)
253 { // note, this func does not validate index
254
255     /* resize array and insert */
256     menu->menu_items = realloc(menu->menu_items, (menu->menu_length+2)*sizeof(MenuItem*));
257
258     for (int i = menu->menu_length; i > index; i--) {
259         menu->menu_items[i] = menu->menu_items[i-1];
260     }
261
262     menu->menu_items[index] = menuitem;
263     menu->menu_items[menu->menu_length+1] = 0; // remember null at end   
264     menu->menu_length += 1;
265
266     /* move cursor pos */
267     menu->selected_item = index;
268
269     return 0;
270 }
271
272 int
273 menu_insert_mode(Menu* menu, int insert_index)
274 {
275     char temp[MAX_CONTENTS_LENGTH+1]; // remember null
276     char* new_contents;
277     int insert_pos;
278
279     curs_on();
280
281     // account for multiline items
282     insert_pos = menu->scroll_offset;
283     for (int i = 0; i < insert_index; i++) {
284         insert_pos += item_height(menu->menu_items[i]);
285     }
286
287     /* move cursor to right spot */
288     ungetstr(menu->menu_items[insert_index]->title);
289     mvwgetnstr(menu->sub_win,
290         insert_pos, 
291         0,
292         temp,
293         MAX_CONTENTS_LENGTH
294     );
295     curs_off();
296
297     /* copy out */
298     new_contents = strdup(temp);
299     menu->menu_items[insert_index]->title = new_contents;
300
301     /* delete if empty - maybe move this to a cleanup stage */
302     if (strlen(new_contents) == 0) {
303         delete_item(menu, insert_index);
304     }
305
306     return 0;
307 }
308
309 int
310 menu_driver(Menu* menu, MenuAction action)
311 {
312
313     switch (action) {
314         case MENU_UP:
315             menu->selected_item = menu->selected_item-1 >= 0 ? menu->selected_item-1 : 0;
316             break;
317
318         case MENU_DOWN:
319             menu->selected_item = menu->selected_item+1 <= menu->menu_length-1 ? menu->selected_item+1 : menu->menu_length-1;
320             break;
321
322         case MENU_TOP:
323             menu->selected_item = 0;
324             break;
325
326         case MENU_BOTTOM:
327             menu->selected_item = menu->menu_length-1;
328             break;
329
330         case MENU_MOVE_UP:
331             if (menu->selected_item <= 0) break;
332             swap_item(menu, menu->selected_item, menu->selected_item-1);
333             menu->selected_item -= 1;
334             break;
335
336         case MENU_MOVE_DOWN:
337             if (menu->selected_item >= menu->menu_length-1) break;
338             swap_item(menu, menu->selected_item, menu->selected_item+1);
339             menu->selected_item += 1;
340             break;
341
342         case MENU_DELETE:
343             delete_item(menu, menu->selected_item);
344             break;
345
346         case MENU_APPEND:
347             insert_item(menu, create_blank_menuitem(), menu->menu_length);
348             render_menu(menu); // refresh after inserting
349             menu_insert_mode(menu, menu->selected_item);
350             break;
351
352         case MENU_INSERT_ABOVE:
353             insert_item(menu, create_blank_menuitem(), menu->selected_item);
354             render_menu(menu);
355             menu_insert_mode(menu, menu->selected_item);
356             break;
357
358         case MENU_INSERT_BELOW:
359             insert_item(menu, create_blank_menuitem(), menu->selected_item+1);
360             render_menu(menu);
361             menu_insert_mode(menu, menu->selected_item); // inserted item is cur now
362             break;
363
364         case MENU_EDIT:
365             menu_insert_mode(menu, menu->selected_item);
366             break;
367
368         default: // This is here for debug, disable later
369             fprintf(stderr, "Invalid menu action");
370     }
371
372     return 0;
373 }
374
375 int
376 render_menu(Menu* menu)
377 {
378     /* draw outer menu (prob dont need this every render) */ 
379     /* wclear(menu->menu_win); */
380     wattron(menu->menu_win, COLOR_PAIR(
381         (menu->focused == true) ?
382         TS_MENU_SELECTED: TS_MENU_NONSELECTED       
383     ));
384     mvwprintw(menu->menu_win, 0, MENU_PAD_LEFT, menu->menu_name);
385     wattroff(menu->menu_win, COLOR_PAIR(0));
386
387     /* draw inner menu */
388     wclear(menu->sub_win);
389
390     /* calculate scroll */
391     int visible;
392
393     visible = items_visible(menu, menu->scroll_offset);
394
395     if (menu->selected_item >= menu->scroll_offset+visible) {
396         menu->scroll_offset = clamp(
397             menu->selected_item-items_visible_rev(menu, menu->selected_item)+1,
398             0,
399             floorzero(menu->menu_length-1)
400         );
401
402     } else if (menu->selected_item < menu->scroll_offset) {
403         menu->scroll_offset = clamp(
404             menu->selected_item,
405             0,
406             floorzero(menu->menu_length-1)
407         );
408     }
409
410     /* char abuf[20]; */
411     /* int y; */
412     /* int x; */
413     /* getmaxyx(menu->sub_win, y, x); */
414     /* sprintf(abuf, "%d,%d,%d max:%d,%d", menu->selected_item, visible, menu->scroll_offset,y,x); */
415     /* mvprintw(19, 27, abuf); */
416
417     int curline = 0;
418     for (int i = menu->scroll_offset; i < menu->menu_length; i++) {
419         curline += render_item(menu, i, curline);
420     }
421
422     wrefresh(menu->sub_win);
423     wrefresh(menu->menu_win);
424
425     return 0;
426 }
427
428 int
429 render_item(Menu* menu, int item_index, int start_y)
430 {
431     MenuItem* curitem;
432     curitem = menu->menu_items[item_index];
433
434     /* color selected item */
435     wattron(menu->sub_win, COLOR_PAIR(
436        (item_index == menu->selected_item && menu->focused == true) ? 
437        TS_SELECTED : TS_NONSELECTED
438     ));
439     mvwprintw(menu->sub_win, start_y, 0, curitem->title);
440     wattroff(menu->sub_win, COLOR_PAIR(0));
441
442     /* display number of items */
443     if (strlen(curitem->description) > 0) {
444         mvwprintw(menu->sub_win, start_y+1, 0, curitem->description); 
445     }
446
447     return item_height(curitem);
448 }
449
450 int
451 item_height(MenuItem* menuitem)
452 {
453     int lines;
454
455     lines = 1;
456     if (strlen(menuitem->description) > 0) {
457         lines += 1;
458     }
459
460     return lines;
461 }
462
463 int
464 items_visible(Menu* menu, int offset)
465 {
466     int maxheight;
467     int maxwidth; // unused
468
469     getmaxyx(menu->sub_win, maxheight, maxwidth);
470
471     int vis = 0;
472     int lines = 0;
473     for (int i = offset ; i < menu->menu_length; i++) {
474
475         lines += item_height(menu->menu_items[i]);
476         if (lines > maxheight) break;
477         vis += 1;
478
479     }
480
481     return vis;
482 }
483
484 int
485 items_visible_rev(Menu* menu, int offset)
486 {
487     int maxheight;
488     int maxwidth; // unused
489
490     getmaxyx(menu->sub_win, maxheight, maxwidth);
491
492     int vis = 0;
493     int lines = 0;
494     for (int i = offset; i > 0; i--) {
495
496         lines += item_height(menu->menu_items[i]);
497         if (lines > maxheight) break;
498         vis +=1;
499
500     }
501
502     return vis;
503 }
504
505 int
506 free_menu(Menu* menu)
507 {
508     return 0;
509 }
510