diff options
Diffstat (limited to 'ui/twin/pbt-menu.c')
-rw-r--r-- | ui/twin/pbt-menu.c | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/ui/twin/pbt-menu.c b/ui/twin/pbt-menu.c new file mode 100644 index 0000000..844b1e1 --- /dev/null +++ b/ui/twin/pbt-menu.c @@ -0,0 +1,515 @@ +/* + * Copyright Geoff Levand <geoff@infradead.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#define _GNU_SOURCE +#include <assert.h> +#include <string.h> +#include <linux/input.h> + +#include "log/log.h" +#include "talloc/talloc.h" +#include "ui/common/ui-system.h" + +#include "pbt-menu.h" + +static void pbt_item_draw_cb(twin_window_t *window) +{ + struct pbt_item *item = pbt_item_from_window(window); + twin_pixmap_t *image; + + assert(window == item->window); + + pbt_dump_item(item); + + //pbt_dump_pixmap(window->pixmap); + + if (pbt_item_is_selected(item) && item->menu->has_focus) + image = item->pixmap_active; + else if (pbt_item_is_selected(item) && !item->menu->has_focus) + image = item->pixmap_selected; + else + image = item->pixmap_idle; + + pbt_image_draw(window->pixmap, image); +} + +static twin_bool_t pbt_item_event_cb(twin_window_t *window, + twin_event_t *event) +{ + struct pbt_item *item = pbt_item_from_window(window); + + pbt_dump_event(pbt_item_name(item), window, event); + + switch(event->kind) { + case TwinEventButtonDown: + if (item->on_execute) + item->on_execute(item); + break; + case TwinEventButtonUp: + break; + case TwinEventMotion: + /* prevent window drag */ + return TWIN_TRUE; + case TwinEventEnter: + pbt_item_set_as_selected(item); + break; + case TwinEventLeave: + break; + case TwinEventKeyDown: + switch(event->u.key.key) { + case (twin_keysym_t)XK_Return: + case (twin_keysym_t)KEY_ENTER: + if (item->on_execute) + item->on_execute(item); + break; + case (twin_keysym_t)'e': + if (item->on_edit) + item->on_edit(item); + break; + default: + break; + } + break; + default: + break; + } + return TWIN_FALSE; +} + +int pbt_item_editor(struct pbt_item *item) +{ + (void)item; + return -1; +} + +struct pbt_item *pbt_item_create(struct pbt_menu *menu, const char *name, + unsigned int position, const char *icon_filename, const char *title, + const char *text) +{ + struct pbt_quad q; + struct pbt_item *item; + twin_pixmap_t *icon; + twin_operand_t src; + const struct pbt_menu_layout *layout = &menu->layout; + twin_path_t *path; + static const twin_argb32_t focus_color = 0x10404040; + enum { + corner_rounding = 8, + stroke_width = 2, + }; + + assert(menu); + + DBGS("%d:%s (%s)\n", position, name, icon_filename); + + item = talloc_zero(menu, struct pbt_item); + + if (!item) + return NULL; + + item->menu = menu; + item->on_edit = pbt_item_editor; + + pbt_menu_get_item_quad(menu, position, &q); + + item->window = twin_window_create(menu->scr->tscreen, + TWIN_ARGB32, TwinWindowPlain, + q.x, q.y, q.width, q.height); + + if (!item->window) + goto fail_window_create; + + twin_window_set_name(item->window, name); + item->window->client_data = item; + item->window->draw = pbt_item_draw_cb; + item->window->event = pbt_item_event_cb; + + item->pixmap_idle = twin_pixmap_create(TWIN_ARGB32, q.width, q.height); + assert(item->pixmap_idle); + + item->pixmap_selected = twin_pixmap_create(TWIN_ARGB32, q.width, + q.height); + assert(item->pixmap_selected); + + item->pixmap_active = twin_pixmap_create(TWIN_ARGB32, q.width, + q.height); + assert(item->pixmap_active); + + if (!item->pixmap_idle || !item->pixmap_selected || !item->pixmap_active) + goto fail_pixmap_create; + + twin_fill(item->pixmap_idle, 0x01000000, TWIN_SOURCE, 0, 0, q.width, + q.height); + + /* Add item icon */ + + icon = pbt_icon_load(icon_filename); + + if (!icon) + goto fail_icon; + + src.source_kind = TWIN_PIXMAP; + src.u.pixmap = icon; + + twin_composite(item->pixmap_idle, + //0, (item->pixmap_idle->height - icon->height) / 2, + 0, 0, + &src, 0, 0, + NULL, 0, 0, + TWIN_SOURCE, + icon->width, icon->height); + + /* Add item text */ + + path = twin_path_create(); + assert(path); + + if (title) { + twin_path_set_font_size(path, + twin_int_to_fixed(layout->title.font_size)); + twin_path_set_font_style(path, TWIN_TEXT_UNHINTED); + + twin_path_move(path, + twin_int_to_fixed(icon->width + layout->text_space), + twin_int_to_fixed(layout->title.font_size + + layout->text_space)); + twin_path_utf8(path, title); + twin_paint_path(item->pixmap_idle, layout->title.color, path); + twin_path_empty(path); + } + + if (text) { + twin_path_set_font_size(path, + twin_int_to_fixed(layout->text.font_size)); + twin_path_move(path, + twin_int_to_fixed(icon->width + layout->text_space), + twin_int_to_fixed(layout->title.font_size + + layout->text.font_size + + layout->text_space)); + twin_path_utf8(path, text); + twin_paint_path(item->pixmap_idle, layout->text.color, path); + twin_path_empty(path); + } + + pbt_image_draw(item->pixmap_selected, item->pixmap_idle); + pbt_image_draw(item->pixmap_active, item->pixmap_idle); + +if (0) { + static const struct pbt_border grey_border = { + .right = 1, + .left = 1, + .top = 1, + .bottom = 1, + .fill_color = 0xffe0e0e0, + }; + + //pbt_border_draw(item->pixmap_idle, &pbt_blue_debug_border); + pbt_border_draw(item->pixmap_selected, &grey_border); + pbt_border_draw(item->pixmap_active, &pbt_green_debug_border); +} else { + assert(!(stroke_width % 2)); + + /* pixmap_selected */ + + twin_path_rounded_rectangle(path, + twin_int_to_fixed(stroke_width / 2), + twin_int_to_fixed(stroke_width / 2), + twin_int_to_fixed(item->pixmap_selected->width - stroke_width), + twin_int_to_fixed(item->pixmap_selected->height - stroke_width), + twin_int_to_fixed(corner_rounding), + twin_int_to_fixed(corner_rounding)); + + twin_paint_stroke(item->pixmap_selected, focus_color, path, + twin_int_to_fixed(stroke_width)); + + twin_path_empty(path); + + /* pixmap_active */ + + twin_path_rounded_rectangle(path, 0, 0, + twin_int_to_fixed(item->pixmap_active->width), + twin_int_to_fixed(item->pixmap_active->height), + twin_int_to_fixed(corner_rounding), + twin_int_to_fixed(corner_rounding)); + + twin_paint_path(item->pixmap_active, focus_color, path); + + twin_path_empty(path); // FIXME: need it??? +} + twin_path_destroy(path); + + list_add_tail(menu->item_list, &item->list); + + pbt_item_redraw(item); + + return item; + +fail_window_create: +fail_pixmap_create: +fail_icon: + return NULL; +} + +void _pbt_dump_item(const struct pbt_item* item, const char *func, int line) +{ + DBG("%s:%d: %p: %sselected, %sfocus\n", func, line, item, + (pbt_item_is_selected(item) ? "+" : "-"), + (item->menu->has_focus ? "+" : "-")); +} + +/** + * pbt_menu_get_item_quad - Return item coords relative to screen origin. + */ + +struct pbt_quad *pbt_menu_get_item_quad(const struct pbt_menu *menu, + unsigned int pos, struct pbt_quad *q) +{ + const struct pbt_menu_layout *layout = &menu->layout; + + q->x = menu->window->pixmap->x + layout->item_space; + + q->width = menu->window->pixmap->width - 2 * layout->item_space; + + q->y = menu->window->pixmap->y + layout->item_space + + pos * (layout->item_height + layout->item_space); + + q->height = layout->item_height; + + return q; +} + +static void pbt_menu_draw_cb(twin_window_t *window) +{ + struct pbt_menu *menu = pbt_menu_from_window(window); + twin_path_t *path = twin_path_create(); + + assert(path); + + pbt_dump_pixmap(window->pixmap); + + twin_fill(window->pixmap, menu->background_color, TWIN_SOURCE, + 0, 0, window->pixmap->width, window->pixmap->height); + + pbt_border_draw(window->pixmap, &menu->border); + + twin_path_destroy(path); +} + +static twin_bool_t pbt_menu_event_cb(twin_window_t *window, + twin_event_t *event) +{ + struct pbt_menu *menu = pbt_menu_from_window(window); + struct pbt_item *i; + + pbt_dump_event(pbt_menu_name(menu), window, event); + + switch(event->kind) { + case TwinEventButtonDown: + case TwinEventButtonUp: + case TwinEventMotion: + /* prevent window drag */ + return TWIN_TRUE; + case TwinEventEnter: + pbt_menu_set_focus(menu, 1); + break; + case TwinEventLeave: + if (!pbt_window_contains(window, event)) + pbt_menu_set_focus(menu, 0); + break; + case TwinEventKeyDown: + switch(event->u.key.key) { + case (twin_keysym_t)XK_Up: + case (twin_keysym_t)KEY_UP: + i = list_prev_entry(menu->item_list, menu->selected, + list); + if (i) + pbt_item_set_as_selected(i); + break; + case (twin_keysym_t)XK_Down: + case (twin_keysym_t)KEY_DOWN: + i = list_next_entry(menu->item_list, menu->selected, + list); + if (i) + pbt_item_set_as_selected(i); + break; + case (twin_keysym_t)XK_Left: + case (twin_keysym_t)KEY_LEFT: + if (menu->parent) { + pbt_menu_set_focus(menu, 0); + pbt_menu_set_focus(menu->parent, 1); + } else + DBGS("no parent\n"); + break; + case (twin_keysym_t)XK_Right: + case (twin_keysym_t)KEY_RIGHT: + if (menu->selected->sub_menu) { + pbt_menu_set_focus(menu, 0); + pbt_menu_set_focus(menu->selected->sub_menu, 1); + } else + DBGS("no sub_menu\n"); + break; + default: + return pbt_item_event_cb(menu->selected->window, event); + } + break; + default: + break; + } + return TWIN_FALSE; +} + +struct pbt_menu *pbt_menu_create(void *talloc_ctx, const char *name, + struct pbt_scr *scr, struct pbt_menu *parent, const struct pbt_quad *q, + const struct pbt_menu_layout *layout) +{ + struct pbt_menu *menu; + + assert(scr); + + DBGS("%s\n", name); + + menu = talloc_zero(talloc_ctx, struct pbt_menu); + + if (!menu) + return NULL; + + menu->scr = scr; + menu->parent = parent; + menu->layout = *layout; + + menu->item_list = talloc(menu, struct list); + list_init(menu->item_list); + + menu->window = twin_window_create(scr->tscreen, TWIN_ARGB32, + TwinWindowPlain, q->x, q->y, + q->width, q->height); + + if (!menu->window) + goto fail_window; + + DBGS("window = %p\n", menu->window); + + twin_window_set_name(menu->window, name); + + menu->background_color = 0x01000000; //FIXME: what value??? + + menu->window->draw = pbt_menu_draw_cb; + menu->window->event = pbt_menu_event_cb; + menu->window->client_data = menu; + + pbt_dump_pixmap(menu->window->pixmap); + + pbt_menu_redraw(menu); + + return menu; + +fail_window: + assert(0); + talloc_free(menu); + return NULL; +} + +void pbt_menu_set_focus(struct pbt_menu *menu, int focus) +{ + DBGS("%s(%p): %d -> %d\n", pbt_menu_name(menu), menu, menu->has_focus, + focus); + + assert(menu->selected); + + if (!menu->has_focus == !focus) + return; + + menu->has_focus = !!focus; + + /* Route key events to menu with focus. */ + + if (menu->has_focus) + menu->scr->tscreen->active = menu->window->pixmap; + + pbt_item_redraw(menu->selected); +} + +void pbt_menu_hide(struct pbt_menu *menu) +{ + struct pbt_item *item; + + if (!menu) + return; + + list_for_each_entry(menu->item_list, item, list) { + if (item->sub_menu) + pbt_menu_hide(item->sub_menu); + + twin_window_hide(item->window); + //twin_window_queue_paint(item->window); + } + + twin_window_hide(menu->window); + //twin_window_queue_paint(menu->window); +} + +void pbt_menu_show(struct pbt_menu *menu, int hide) +{ + struct pbt_item *item; + + if (!menu) + return; + + twin_window_show(menu->window); + pbt_menu_redraw(menu); + + list_for_each_entry(menu->item_list, item, list) { + twin_window_show(item->window); + pbt_item_redraw(item); + + if (item->sub_menu) { + if (pbt_item_is_selected(item)) + pbt_menu_show(item->sub_menu, hide); + else if (hide) + pbt_menu_hide(item->sub_menu); + } + } +} + +void pbt_menu_set_selected(struct pbt_menu *menu, struct pbt_item *item) +{ + struct pbt_item *last_selected; + + assert(item); + + DBGS("%s(%p): %s(%p) -> %s(%p)\n", pbt_menu_name(menu), menu, + (menu->selected ? pbt_menu_name(menu) : "none"), + menu->selected, pbt_item_name(item), item); + + if (menu->selected == item) + return; + + last_selected = menu->selected; + menu->selected = item; + + if (last_selected) { + pbt_menu_hide(last_selected->sub_menu); + pbt_item_redraw(last_selected); + } + + pbt_item_redraw(item); + pbt_menu_show(item->sub_menu, 0); +} |