#include "internal.h" #include #include #include #include #include #include /** * Default font. */ static const char *default_font = "monospace 10"; /** * Default hexadecimal colors. */ static const char *default_colors[BM_COLOR_LAST] = { "#121212FF", // BM_COLOR_TITLE_BG "#D81860FF", // BM_COLOR_TITLE_FG "#121212FF", // BM_COLOR_FILTER_BG "#CACACAFF", // BM_COLOR_FILTER_FG "#121212FF", // BM_COLOR_CURSOR_BG "#CACACAFF", // BM_COLOR_CURSOR_FG "#121212FF", // BM_COLOR_ITEM_BG "#CACACAFF", // BM_COLOR_ITEM_FG "#121212FF", // BM_COLOR_HIGHLIGHTED_BG "#D81860FF", // BM_COLOR_HIGHLIGHTED_FG "#D81860FF", // BM_COLOR_FEEDBACK_BG "#121212FF", // BM_COLOR_FEEDBACK_FG "#121212FF", // BM_COLOR_SELECTED_BG "#D81860FF", // BM_COLOR_SELECTED_FG "#121212FF", // BM_COLOR_ALTERNATE_BG "#CACACAFF", // BM_COLOR_ALTERNATE_FG "#121212FF", // BM_COLOR_SCROLLBAR_BG "#D81860FF", // BM_COLOR_SCROLLBAR_FG "#D81860FF", // BM_COLOR_BORDER }; /** * Filter function map. */ static struct bm_item** (*filter_func[BM_FILTER_MODE_LAST])(struct bm_menu *menu, bool addition, uint32_t *out_nmemb) = { bm_filter_dmenu, /* BM_FILTER_DMENU */ bm_filter_dmenu_case_insensitive /* BM_FILTER_DMENU_CASE_INSENSITIVE */ }; struct bm_menu* bm_menu_new(const char *renderer) { struct bm_menu *menu; if (!(menu = calloc(1, sizeof(struct bm_menu)))) return NULL; menu->dirty = true; uint32_t count; const struct bm_renderer **renderers = bm_get_renderers(&count); const char *name = secure_getenv("BEMENU_BACKEND"); name = (name && strlen(name) > 0 ? name : NULL); for (uint32_t i = 0; i < count; ++i) { if (!name && !renderer && renderers[i]->api.priorty != BM_PRIO_GUI) continue; if ((renderer && strcmp(renderer, renderers[i]->name)) || (name && strcmp(name, renderers[i]->name))) continue; if (renderers[i]->api.priorty == BM_PRIO_TERMINAL) { /** * Some sanity checks that we are in terminal. * These however are not reliable, thus we don't auto-detect terminal based renderers. * These will be only used when explicitly requested. * * Launching terminal based menu instance at background is not a good idea. */ const char *term = getenv("TERM"); if (!term || !strlen(term) || getppid() == 1) continue; } if (bm_renderer_activate((struct bm_renderer*)renderers[i], menu)) break; } if (!menu->renderer) goto fail; if (!bm_menu_set_font(menu, NULL)) goto fail; for (uint32_t i = 0; i < BM_COLOR_LAST; ++i) { if (!bm_menu_set_color(menu, i, NULL)) goto fail; } if (!(menu->filter_item = bm_item_new(NULL))) goto fail; return menu; fail: bm_menu_free(menu); return NULL; } void bm_menu_free(struct bm_menu *menu) { assert(menu); if (menu->renderer && menu->renderer->api.destructor) menu->renderer->api.destructor(menu); free(menu->title); free(menu->filter); free(menu->old_filter); free(menu->font.name); free(menu->monitor_name); for (uint32_t i = 0; i < BM_COLOR_LAST; ++i) free(menu->colors[i].hex); bm_menu_free_items(menu); free(menu); } void bm_menu_free_items(struct bm_menu *menu) { assert(menu); list_free_list(&menu->selection); list_free_list(&menu->filtered); list_free_items(&menu->items, (list_free_fun)bm_item_free); if (menu->filter_item) free(menu->filter_item); } const struct bm_renderer* bm_menu_get_renderer(struct bm_menu *menu) { assert(menu); return menu->renderer; } void bm_menu_set_userdata(struct bm_menu *menu, void *userdata) { assert(menu); menu->userdata = userdata; } void* bm_menu_get_userdata(struct bm_menu *menu) { assert(menu); return menu->userdata; } void bm_menu_set_prefix(struct bm_menu *menu, const char *prefix) { assert(menu); free(menu->prefix); menu->prefix = (prefix && strlen(prefix) > 0 ? bm_strdup(prefix) : NULL); } const char* bm_menu_get_prefix(struct bm_menu *menu) { assert(menu); return menu->prefix; } void bm_menu_set_filter(struct bm_menu *menu, const char *filter) { assert(menu); if (strcmp(menu->filter ? menu->filter : "", filter ? filter : "")) menu->dirty = true; free(menu->filter); menu->filter_size = (filter ? strlen(filter) : 0); menu->filter = (menu->filter_size > 0 ? bm_strdup(filter) : NULL); menu->curses_cursor = (menu->filter ? bm_utf8_string_screen_width(menu->filter) : 0); menu->dirty |= (menu->cursor != menu->filter_size); menu->cursor = menu->filter_size; } const char* bm_menu_get_filter(struct bm_menu *menu) { assert(menu); return menu->filter; } void bm_menu_set_filter_mode(struct bm_menu *menu, enum bm_filter_mode mode) { assert(menu); menu->filter_mode = (mode >= BM_FILTER_MODE_LAST ? BM_FILTER_MODE_DMENU : mode); } enum bm_filter_mode bm_menu_get_filter_mode(const struct bm_menu *menu) { assert(menu); return menu->filter_mode; } void bm_menu_set_lines(struct bm_menu *menu, uint32_t lines) { assert(menu); menu->lines = lines; } uint32_t bm_menu_get_lines(struct bm_menu *menu) { assert(menu); return menu->lines; } void bm_menu_set_wrap(struct bm_menu *menu, bool wrap) { assert(menu); menu->wrap = wrap; } bool bm_menu_get_wrap(const struct bm_menu *menu) { assert(menu); return menu->wrap; } bool bm_menu_set_title(struct bm_menu *menu, const char *title) { assert(menu); char *copy = NULL; if (title && !(copy = bm_strdup(title))) return false; free(menu->title); menu->title = copy; return true; } const char* bm_menu_get_title(const struct bm_menu *menu) { assert(menu); return menu->title; } bool bm_menu_set_font(struct bm_menu *menu, const char *font) { assert(menu); const char *nfont = (font ? font : default_font); char *copy = NULL; if (!(copy = bm_strdup(nfont))) return false; free(menu->font.name); menu->font.name = copy; return true; } const char* bm_menu_get_font(const struct bm_menu *menu) { assert(menu); return menu->font.name; } void bm_menu_set_line_height(struct bm_menu *menu, uint32_t line_height) { assert(menu); menu->line_height = line_height; } uint32_t bm_menu_get_line_height(struct bm_menu *menu) { assert(menu); return menu->line_height; } void bm_menu_set_cursor_height(struct bm_menu *menu, uint32_t cursor_height) { assert(menu); menu->cursor_height = cursor_height; } uint32_t bm_menu_get_cursor_height(struct bm_menu *menu) { assert(menu); return menu->cursor_height; } void bm_menu_set_cursor_width(struct bm_menu *menu, uint32_t cursor_width) { assert(menu); menu->cursor_width = cursor_width; } uint32_t bm_menu_get_cursor_width(struct bm_menu *menu) { assert(menu); return menu->cursor_width; } void bm_menu_set_hpadding(struct bm_menu *menu, uint32_t hpadding) { assert(menu); menu->hpadding = hpadding; } uint32_t bm_menu_get_hpadding(struct bm_menu *menu) { assert(menu); return menu->hpadding; } void bm_menu_set_border_size(struct bm_menu* menu, uint32_t border_size) { assert(menu); menu->border_size = border_size; } uint32_t bm_menu_get_border_size(struct bm_menu* menu) { assert(menu); return menu->border_size; } uint32_t bm_menu_get_height(struct bm_menu *menu) { assert(menu); uint32_t height = 0; if (menu->renderer->api.get_height) { height = menu->renderer->api.get_height(menu); } return height; } uint32_t bm_menu_get_width(struct bm_menu *menu) { assert(menu); uint32_t width = 0; if (menu->renderer->api.get_width) { width = menu->renderer->api.get_width(menu); } return width; } bool bm_menu_set_color(struct bm_menu *menu, enum bm_color color, const char *hex) { assert(menu); const char *nhex = (hex ? hex : default_colors[color]); unsigned int r, g, b, a = 255; int matched = sscanf(nhex, "#%2x%2x%2x%2x", &r, &b, &g, &a); if (matched != 3 && matched != 4) return false; char *copy = NULL; if (!(copy = bm_strdup(nhex))) return false; free(menu->colors[color].hex); menu->colors[color].hex = copy; menu->colors[color].r = r; menu->colors[color].g = g; menu->colors[color].b = b; menu->colors[color].a = a; return true; } const char* bm_menu_get_color(const struct bm_menu *menu, enum bm_color color) { assert(menu); return menu->colors[color].hex; } void bm_menu_set_scrollbar(struct bm_menu *menu, enum bm_scrollbar_mode mode) { menu->scrollbar = (mode == BM_SCROLLBAR_LAST ? BM_SCROLLBAR_NONE : mode); } enum bm_scrollbar_mode bm_menu_get_scrollbar(struct bm_menu *menu) { return menu->scrollbar; } void bm_menu_set_align(struct bm_menu *menu, enum bm_align align) { assert(menu); if(menu->align == align) return; menu->align = align; if (menu->renderer->api.set_align) menu->renderer->api.set_align(menu, align); } enum bm_align bm_menu_get_align(struct bm_menu *menu) { assert(menu); return menu->align; } void bm_menu_set_width(struct bm_menu *menu, uint32_t margin, float factor) { assert(menu); if(menu->hmargin_size == margin && menu->width_factor == factor) return; menu->hmargin_size = margin; menu->width_factor = factor; if(menu->renderer->api.set_width) menu->renderer->api.set_width(menu, margin, factor); } uint32_t bm_menu_get_hmargin_size(struct bm_menu *menu) { assert(menu); return menu->hmargin_size; } float bm_menu_get_width_factor(struct bm_menu *menu) { assert(menu); return menu->width_factor; } void bm_menu_set_monitor(struct bm_menu *menu, int32_t monitor) { assert(menu); if (menu->monitor == monitor) return; menu->monitor = monitor; if (menu->renderer->api.set_monitor) menu->renderer->api.set_monitor(menu, monitor); } void bm_menu_set_monitor_name(struct bm_menu *menu, char *monitor_name) { assert(menu); if (!monitor_name) return; if (menu->monitor_name && !strcmp(menu->monitor_name, monitor_name)) return; menu->monitor_name = bm_strdup(monitor_name); if (menu->renderer->api.set_monitor_name) menu->renderer->api.set_monitor_name(menu, monitor_name); } uint32_t bm_menu_get_monitor(struct bm_menu *menu) { assert(menu); return menu->monitor; } void bm_menu_grab_keyboard(struct bm_menu *menu, bool grab) { assert(menu); if (menu->grabbed == grab) return; menu->grabbed = grab; if (menu->renderer->api.grab_keyboard) menu->renderer->api.grab_keyboard(menu, grab); } bool bm_menu_is_keyboard_grabbed(struct bm_menu *menu) { return menu->grabbed; } void bm_menu_set_panel_overlap(struct bm_menu *menu, bool overlap) { assert(menu); if (menu->overlap == overlap) return; menu->overlap = overlap; if (menu->renderer->api.set_overlap) menu->renderer->api.set_overlap(menu, overlap); } void bm_menu_set_spacing(struct bm_menu *menu, bool spacing) { assert(menu); if (menu->spacing == spacing) return; menu->spacing = spacing; } void bm_menu_set_password(struct bm_menu *menu, bool password) { assert(menu); if (menu->password == password) return; menu->password = password; } bool bm_menu_get_password(struct bm_menu *menu) { assert(menu); return menu->password; } bool bm_menu_add_item_at(struct bm_menu *menu, struct bm_item *item, uint32_t index) { assert(menu); return list_add_item_at(&menu->items, item, index); } bool bm_menu_add_item(struct bm_menu *menu, struct bm_item *item) { return list_add_item(&menu->items, item); } bool bm_menu_remove_item_at(struct bm_menu *menu, uint32_t index) { assert(menu); if (!menu->items.items || menu->items.count <= index) return 0; struct bm_item *item = ((struct bm_item**)menu->items.items)[index]; bool ret = list_remove_item_at(&menu->items, index); if (ret) { list_remove_item(&menu->selection, item); list_remove_item(&menu->filtered, item); } return ret; } bool bm_menu_remove_item(struct bm_menu *menu, struct bm_item *item) { assert(menu); bool ret = list_remove_item(&menu->items, item); if (ret) { list_remove_item(&menu->selection, item); list_remove_item(&menu->filtered, item); } return ret; } bool bm_menu_set_highlighted_index(struct bm_menu *menu, uint32_t index) { assert(menu); uint32_t count; bm_menu_get_filtered_items(menu, &count); if (count <= index) return 0; if (menu->index != index) menu->dirty = true; return (menu->index = index); } bool bm_menu_set_highlighted_item(struct bm_menu *menu, struct bm_item *item) { assert(menu); uint32_t i, count; struct bm_item **items = bm_menu_get_filtered_items(menu, &count); for (i = 0; i < count && items[i] != item; ++i); if (count <= i) return 0; return (bm_menu_set_highlighted_index(menu, i)); } struct bm_item* bm_menu_get_highlighted_item(const struct bm_menu *menu) { assert(menu); uint32_t count; struct bm_item **items = bm_menu_get_filtered_items(menu, &count); if (!items || count <= menu->index) return NULL; return items[menu->index]; } bool bm_menu_set_selected_items(struct bm_menu *menu, struct bm_item **items, uint32_t nmemb) { assert(menu); struct bm_item **new_items; if (!(new_items = calloc(sizeof(struct bm_item*), nmemb))) return 0; memcpy(new_items, items, sizeof(struct bm_item*) * nmemb); return list_set_items_no_copy(&menu->selection, new_items, nmemb); } struct bm_item** bm_menu_get_selected_items(const struct bm_menu *menu, uint32_t *out_nmemb) { assert(menu); return list_get_items(&menu->selection, out_nmemb); } bool bm_menu_set_items(struct bm_menu *menu, const struct bm_item **items, uint32_t nmemb) { assert(menu); bool ret = list_set_items(&menu->items, items, nmemb, (list_free_fun)bm_item_free); if (ret) { list_free_list(&menu->selection); list_free_list(&menu->filtered); } return ret; } struct bm_item** bm_menu_get_items(const struct bm_menu *menu, uint32_t *out_nmemb) { assert(menu); return list_get_items(&menu->items, out_nmemb); } struct bm_item** bm_menu_get_filtered_items(const struct bm_menu *menu, uint32_t *out_nmemb) { assert(menu); if (menu->filter && strlen(menu->filter)) return list_get_items(&menu->filtered, out_nmemb); return list_get_items(&menu->items, out_nmemb); } bool bm_menu_render(struct bm_menu *menu) { assert(menu); if (menu->renderer->api.render) return menu->renderer->api.render(menu); return true; } void bm_menu_filter(struct bm_menu *menu) { assert(menu); char addition = 0; size_t len = (menu->filter ? strlen(menu->filter) : 0); if (!len || !menu->items.items || menu->items.count <= 0) { list_free_list(&menu->filtered); free(menu->old_filter); menu->old_filter = NULL; return; } if (menu->old_filter) { size_t oldLen = strlen(menu->old_filter); addition = (oldLen < len && !memcmp(menu->old_filter, menu->filter, oldLen)); } if (menu->old_filter && addition && menu->filtered.count <= 0) return; if (menu->old_filter && !strcmp(menu->filter, menu->old_filter)) return; uint32_t count; struct bm_item **filtered = filter_func[menu->filter_mode](menu, addition, &count); list_set_items_no_copy(&menu->filtered, filtered, count); bm_menu_set_highlighted_index(menu, 0); free(menu->old_filter); menu->old_filter = bm_strdup(menu->filter); } enum bm_key bm_menu_poll_key(struct bm_menu *menu, uint32_t *out_unicode) { assert(menu && out_unicode); *out_unicode = 0; enum bm_key key = BM_KEY_NONE; if (menu->renderer->api.poll_key) key = menu->renderer->api.poll_key(menu, out_unicode); return key; } struct bm_pointer bm_menu_poll_pointer(struct bm_menu *menu) { assert(menu); struct bm_pointer pointer = {0}; if (menu->renderer->api.poll_pointer) pointer = menu->renderer->api.poll_pointer(menu); return pointer; } struct bm_touch bm_menu_poll_touch(struct bm_menu *menu) { assert(menu); struct bm_touch touch = {0}; if (menu->renderer->api.poll_touch) touch = menu->renderer->api.poll_touch(menu); return touch; } void bm_menu_release_touch(struct bm_menu *menu) { assert(menu); if (menu->renderer->api.release_touch) menu->renderer->api.release_touch(menu); } static void menu_next(struct bm_menu *menu, uint32_t count, bool wrap) { if (menu->index < count - 1) { bm_menu_set_highlighted_index(menu, menu->index + 1); } else if (wrap) { bm_menu_set_highlighted_index(menu, 0); } } static void menu_prev(struct bm_menu *menu, uint32_t count, bool wrap) { if (menu->index > 0) { bm_menu_set_highlighted_index(menu, menu->index - 1); } else if (wrap) { bm_menu_set_highlighted_index(menu, count - 1); } } static void menu_point_select(struct bm_menu *menu, uint32_t posx, uint32_t posy, uint32_t displayed) { (void) posx; if (displayed == 0) { return; } uint32_t line_height = bm_menu_get_height(menu) / displayed; if (line_height == 0) { return; } uint32_t selected_line = posy / line_height; if (0 == selected_line) { // Mouse over title bar return; } assert(menu->lines != 0); uint16_t current_page_index = menu->index / menu->lines; if (selected_line >= displayed) { // This might be useless return; } bm_menu_set_highlighted_index(menu, current_page_index * menu->lines + (selected_line - 1)); } static void menu_scroll_down(struct bm_menu *menu, uint16_t count) { assert(menu->lines != 0); if (menu->index / menu->lines != count / menu->lines) { // not last page bm_menu_set_highlighted_index(menu, ((menu->index / menu->lines) + 1) * menu->lines); } } static void menu_scroll_up(struct bm_menu *menu, uint16_t count) { (void) count; assert(menu->lines != 0); if (menu->index / menu->lines) { // not first page bm_menu_set_highlighted_index(menu, ((menu->index / menu->lines) - 1) * menu->lines + menu->lines - 1); } } static void menu_scroll_first(struct bm_menu *menu, uint16_t count) { (void) count; bm_menu_set_highlighted_index(menu, 0); } static void menu_scroll_last(struct bm_menu *menu, uint16_t count) { bm_menu_set_highlighted_index(menu, count - 1); } void bm_menu_add_event_feedback(struct bm_menu *menu, uint32_t event_feedback) { menu->event_feedback |= event_feedback; } void bm_menu_remove_event_feedback(struct bm_menu *menu, uint32_t event_feedback) { menu->event_feedback &= ~event_feedback; } void bm_menu_set_event_feedback(struct bm_menu *menu, uint32_t event_feedback) { if (menu->event_feedback != event_feedback) { menu->dirty = true; } menu->event_feedback = event_feedback; } enum bm_run_result bm_menu_run_with_key(struct bm_menu *menu, enum bm_key key, uint32_t unicode) { assert(menu); uint32_t count; bm_menu_get_filtered_items(menu, &count); uint32_t displayed = 0; if (menu->renderer->api.get_displayed_count) displayed = menu->renderer->api.get_displayed_count(menu); if (!displayed) displayed = count; if (key != BM_KEY_NONE) menu->dirty = true; switch (key) { case BM_KEY_LEFT: if (menu->filter) { const uint32_t old_cursor = menu->cursor; menu->cursor -= bm_utf8_rune_prev(menu->filter, menu->cursor); menu->curses_cursor -= bm_utf8_rune_width(menu->filter + menu->cursor, old_cursor - menu->cursor); } break; case BM_KEY_RIGHT: if (menu->filter) { const uint32_t old_cursor = menu->cursor; menu->cursor += bm_utf8_rune_next(menu->filter, menu->cursor); menu->curses_cursor += bm_utf8_rune_width(menu->filter + old_cursor, menu->cursor - old_cursor); } break; case BM_KEY_HOME: menu->curses_cursor = menu->cursor = 0; break; case BM_KEY_END: menu->cursor = (menu->filter ? strlen(menu->filter) : 0); menu->curses_cursor = (menu->filter ? bm_utf8_string_screen_width(menu->filter) : 0); break; case BM_KEY_UP: menu_prev(menu, count, menu->wrap); break; case BM_KEY_DOWN: menu_next(menu, count, menu->wrap); break; case BM_KEY_PAGE_UP: bm_menu_set_highlighted_index(menu, (menu->index < displayed ? 0 : menu->index - (displayed - 1))); break; case BM_KEY_PAGE_DOWN: bm_menu_set_highlighted_index(menu, (menu->index + displayed >= count ? count - 1 : menu->index + (displayed - 1))); break; case BM_KEY_SHIFT_PAGE_UP: bm_menu_set_highlighted_index(menu, 0); break; case BM_KEY_SHIFT_PAGE_DOWN: bm_menu_set_highlighted_index(menu, count - 1); break; case BM_KEY_BACKSPACE: if (menu->filter) { size_t width; menu->cursor -= bm_utf8_rune_remove(menu->filter, menu->cursor, &width); menu->curses_cursor -= width; } break; case BM_KEY_DELETE: if (menu->filter) { size_t width = bm_utf8_rune_next(menu->filter, menu->cursor); if (width) bm_utf8_rune_remove(menu->filter, menu->cursor + width, NULL); } break; case BM_KEY_LINE_DELETE_LEFT: if (menu->filter) { while (menu->cursor > 0) { size_t width; menu->cursor -= bm_utf8_rune_remove(menu->filter, menu->cursor, &width); menu->curses_cursor -= width; } } break; case BM_KEY_LINE_DELETE_RIGHT: if (menu->filter) menu->filter[menu->cursor] = 0; break; case BM_KEY_WORD_DELETE: if (menu->filter) { while (menu->cursor < strlen(menu->filter) && !isspace(menu->filter[menu->cursor])) { uint32_t oldCursor = menu->cursor; menu->cursor += bm_utf8_rune_next(menu->filter, menu->cursor); menu->curses_cursor += bm_utf8_rune_width(menu->filter + oldCursor, menu->cursor - oldCursor); } while (menu->cursor > 0 && isspace(menu->filter[menu->cursor - 1])) { uint32_t oldCursor = menu->cursor; menu->cursor -= bm_utf8_rune_prev(menu->filter, menu->cursor); menu->curses_cursor -= bm_utf8_rune_width(menu->filter + menu->cursor, oldCursor - menu->cursor); } while (menu->cursor > 0 && !isspace(menu->filter[menu->cursor - 1])) { size_t width; menu->cursor -= bm_utf8_rune_remove(menu->filter, menu->cursor, &width); menu->curses_cursor -= width; } } break; case BM_KEY_PASTE: { FILE *clipboard; clipboard = popen("wl-paste -t text/plain", "r"); if (!clipboard) { clipboard = popen("xclip -t text/plain -out", "r"); } if (!clipboard) { break; } int c; while ((c = fgetc(clipboard)) != EOF) { size_t width; menu->cursor += bm_unicode_insert(&menu->filter, &menu->filter_size, menu->cursor, c, &width); menu->curses_cursor += width; } pclose(clipboard); } break; case BM_KEY_UNICODE: { size_t width; menu->cursor += bm_unicode_insert(&menu->filter, &menu->filter_size, menu->cursor, unicode, &width); menu->curses_cursor += width; } break; case BM_KEY_TAB: { menu_next(menu, count, true); } break; case BM_KEY_SHIFT_TAB: { const char *text; struct bm_item *highlighted = bm_menu_get_highlighted_item(menu); if (highlighted && (text = bm_item_get_text(highlighted))) bm_menu_set_filter(menu, text); } break; case BM_KEY_CONTROL_RETURN: case BM_KEY_RETURN: case BM_KEY_CUSTOM_1: case BM_KEY_CUSTOM_2: case BM_KEY_CUSTOM_3: case BM_KEY_CUSTOM_4: case BM_KEY_CUSTOM_5: case BM_KEY_CUSTOM_6: case BM_KEY_CUSTOM_7: case BM_KEY_CUSTOM_8: case BM_KEY_CUSTOM_9: case BM_KEY_CUSTOM_10: { struct bm_item *highlighted = bm_menu_get_highlighted_item(menu); if (highlighted && !bm_menu_item_is_selected(menu, highlighted)) list_add_item(&menu->selection, highlighted); } break; case BM_KEY_SHIFT_RETURN: /* this will return the filter as selected item below! */ case BM_KEY_ESCAPE: /* this will cancel however */ list_free_list(&menu->selection); break; default: break; } bm_menu_filter(menu); switch (key) { case BM_KEY_CUSTOM_1: case BM_KEY_CUSTOM_2: case BM_KEY_CUSTOM_3: case BM_KEY_CUSTOM_4: case BM_KEY_CUSTOM_5: case BM_KEY_CUSTOM_6: case BM_KEY_CUSTOM_7: case BM_KEY_CUSTOM_8: case BM_KEY_CUSTOM_9: case BM_KEY_CUSTOM_10: case BM_KEY_SHIFT_RETURN: case BM_KEY_RETURN: if (!bm_menu_get_selected_items(menu, NULL)) { bm_item_set_text(menu->filter_item, menu->filter); list_add_item(&menu->selection, menu->filter_item); } switch (key) { case BM_KEY_CUSTOM_1: return BM_RUN_RESULT_CUSTOM_1; case BM_KEY_CUSTOM_2: return BM_RUN_RESULT_CUSTOM_2; case BM_KEY_CUSTOM_3: return BM_RUN_RESULT_CUSTOM_3; case BM_KEY_CUSTOM_4: return BM_RUN_RESULT_CUSTOM_4; case BM_KEY_CUSTOM_5: return BM_RUN_RESULT_CUSTOM_5; case BM_KEY_CUSTOM_6: return BM_RUN_RESULT_CUSTOM_6; case BM_KEY_CUSTOM_7: return BM_RUN_RESULT_CUSTOM_7; case BM_KEY_CUSTOM_8: return BM_RUN_RESULT_CUSTOM_8; case BM_KEY_CUSTOM_9: return BM_RUN_RESULT_CUSTOM_9; case BM_KEY_CUSTOM_10: return BM_RUN_RESULT_CUSTOM_10; default: break; } return BM_RUN_RESULT_SELECTED; case BM_KEY_ESCAPE: return BM_RUN_RESULT_CANCEL; default: break; } return BM_RUN_RESULT_RUNNING; } enum bm_run_result bm_menu_run_with_pointer(struct bm_menu *menu, struct bm_pointer pointer) { uint32_t count; bm_menu_get_filtered_items(menu, &count); uint32_t displayed = 0; if (menu->renderer->api.get_displayed_count) displayed = menu->renderer->api.get_displayed_count(menu); if (!displayed) displayed = count; if (!menu->lines) { if (pointer.axes[BM_POINTER_AXIS_VERTICAL].valid) { if (0 < pointer.axes[BM_POINTER_AXIS_VERTICAL].value) { menu_next(menu, count, menu->wrap); } else { menu_prev(menu, count, menu->wrap); } } if (pointer.event_mask & POINTER_EVENT_BUTTON && pointer.state == POINTER_STATE_RELEASED) { switch (pointer.button) { case BM_POINTER_KEY_PRIMARY: { struct bm_item *highlighted = bm_menu_get_highlighted_item(menu); if (highlighted && !bm_menu_item_is_selected(menu, highlighted)) list_add_item(&menu->selection, highlighted); } return BM_RUN_RESULT_SELECTED; } } return BM_RUN_RESULT_RUNNING; } if (pointer.axes[BM_POINTER_AXIS_VERTICAL].valid) { if (0 < pointer.axes[BM_POINTER_AXIS_VERTICAL].value) { menu_scroll_down(menu, count); } else { menu_scroll_up(menu, count); } } if (pointer.event_mask & POINTER_EVENT_MOTION) { menu_point_select(menu, pointer.pos_x, pointer.pos_y, displayed); } if (pointer.event_mask & POINTER_EVENT_BUTTON && pointer.state == POINTER_STATE_RELEASED) { switch (pointer.button) { case BM_POINTER_KEY_PRIMARY: { struct bm_item *highlighted = bm_menu_get_highlighted_item(menu); if (highlighted && !bm_menu_item_is_selected(menu, highlighted)) list_add_item(&menu->selection, highlighted); } return BM_RUN_RESULT_SELECTED; } } return BM_RUN_RESULT_RUNNING; } enum bm_run_result bm_menu_run_with_touch(struct bm_menu *menu, struct bm_touch touch) { uint32_t count; bm_menu_get_filtered_items(menu, &count); uint32_t displayed = 0; if (menu->renderer->api.get_displayed_count) displayed = menu->renderer->api.get_displayed_count(menu); if (!displayed) displayed = count; if (!menu->lines) { // Not implemented yet return BM_RUN_RESULT_RUNNING; } uint16_t count_down = 0; for (size_t i = 0; i < 2; ++i) { struct bm_touch_point point = touch.points[i]; if (point.event_mask & TOUCH_EVENT_DOWN) { count_down += 1; } } if (count_down == 2) { int16_t scroll_count = 0; int16_t scroll_directions[2]; int16_t distance_trigger = displayed * bm_menu_get_line_height(menu) / 4; for (size_t i = 0; i < 2; ++i) { struct bm_touch_point point = touch.points[i]; if (!(point.event_mask & TOUCH_EVENT_DOWN)) continue; int32_t movement_y = point.pos_y - point.start_y; if (abs(movement_y) > distance_trigger) { scroll_directions[i] = movement_y / abs(movement_y); scroll_count++; } } int16_t scroll_direction_sum = scroll_directions[0] + scroll_directions[1]; if (2 == abs(scroll_direction_sum)) { uint16_t current_page = menu->index / menu->lines; if (scroll_direction_sum < 0 && current_page != count / menu->lines) { // not already the first page menu_scroll_down(menu, count); bm_menu_release_touch(menu); } else if (scroll_direction_sum > 0 && current_page) { // not already the first page menu_scroll_up(menu, count); bm_menu_release_touch(menu); } } } if (count_down != 1) return BM_RUN_RESULT_RUNNING; for (size_t i = 0; i < 2; ++i) { struct bm_touch_point point = touch.points[i]; if (!(point.event_mask & TOUCH_EVENT_DOWN)) continue; menu_point_select(menu, point.pos_x, point.pos_y, displayed); if (point.pos_y < (int32_t) (bm_menu_get_height(menu) / displayed)) { if (point.pos_y < ((int32_t) (bm_menu_get_height(menu) / displayed)) - 50) { if (point.event_mask & TOUCH_EVENT_UP) { menu_scroll_first(menu, count); bm_menu_set_event_feedback(menu, 0); } else if (point.event_mask & TOUCH_EVENT_MOTION) { bm_menu_set_event_feedback(menu, TOUCH_WILL_SCROLL_FIRST); } } else { if (point.event_mask & TOUCH_EVENT_UP) { menu_scroll_up(menu, count); bm_menu_set_event_feedback(menu, 0); } else if (point.event_mask & TOUCH_EVENT_MOTION) { bm_menu_set_event_feedback(menu, TOUCH_WILL_SCROLL_UP); } } } else if ((uint32_t) point.pos_y > bm_menu_get_height(menu)) { if ((uint32_t) point.pos_y > bm_menu_get_height(menu) + 50) { if (point.event_mask & TOUCH_EVENT_UP) { menu_scroll_last(menu, count); bm_menu_set_event_feedback(menu, 0); } else if (point.event_mask & TOUCH_EVENT_MOTION) { bm_menu_set_event_feedback(menu, TOUCH_WILL_SCROLL_LAST); } } else { if (point.event_mask & TOUCH_EVENT_UP) { menu_scroll_down(menu, count); bm_menu_set_event_feedback(menu, 0); } else if (point.event_mask & TOUCH_EVENT_MOTION) { bm_menu_set_event_feedback(menu, TOUCH_WILL_SCROLL_DOWN); } } } else { if (point.pos_x > 0 && (uint32_t) point.pos_x < bm_menu_get_width(menu)) { bm_menu_set_event_feedback(menu, 0); if (point.event_mask & TOUCH_EVENT_UP) { { struct bm_item *highlighted = bm_menu_get_highlighted_item(menu); if (highlighted && !bm_menu_item_is_selected(menu, highlighted)) list_add_item(&menu->selection, highlighted); } return BM_RUN_RESULT_SELECTED; } } else { if (!(point.event_mask & TOUCH_EVENT_UP)) { bm_menu_set_event_feedback(menu, TOUCH_WILL_CANCEL); } else { bm_menu_set_event_feedback(menu, 0); } } } } return BM_RUN_RESULT_RUNNING; } enum bm_run_result bm_menu_run_with_events(struct bm_menu *menu, enum bm_key key, struct bm_pointer pointer, struct bm_touch touch, uint32_t unicode) { enum bm_run_result key_result = bm_menu_run_with_key(menu, key, unicode); if (key_result != BM_RUN_RESULT_RUNNING) { return key_result; } enum bm_run_result pointer_result = bm_menu_run_with_pointer(menu, pointer); if (pointer_result != BM_RUN_RESULT_RUNNING) { return pointer_result; } return bm_menu_run_with_touch(menu, touch); } /* vim: set ts=8 sw=4 tw=0 :*/