/* * This file is part of MultiROM. * * MultiROM 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, either version 3 of the License, or * (at your option) any later version. * * MultiROM 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 MultiROM. If not, see . */ #include #include "listview.h" #include "framebuffer.h" #include "util.h" #include "log.h" #include "colors.h" #include "workers.h" #include "input.h" #include "animation.h" #include "notification_card.h" #include "containers.h" #define MARK_W (10*DPI_MUL) #define MARK_H (50*DPI_MUL) #define PADDING (35*DPI_MUL) #define LINE_W (2*DPI_MUL) #define SCROLL_DIST (20*DPI_MUL) #define OVERSCROLL_H (130*DPI_MUL) #define OVERSCROLL_MARK_H (4*DPI_MUL) #define OVERSCROLL_RETURN_SPD (10*DPI_MUL) static int listview_bounceback(UNUSED uint32_t diff, void *data) { listview *v = (listview*)data; const int max = v->fullH - v->h; int step; if(v->pos < 0) { step = imin(-v->pos, OVERSCROLL_RETURN_SPD); listview_update_overscroll_mark(v, 0, -(v->pos+step)); } else if(v->pos > max) { step = -imin(v->pos-max, OVERSCROLL_RETURN_SPD); listview_update_overscroll_mark(v, 1, (v->pos - max + step)); } else { if(v->overscroll_marks[0]->w != 0) v->overscroll_marks[0]->w = 0; if(v->overscroll_marks[1]->w != 0) v->overscroll_marks[1]->w = 0; return 0; } if(v->touch.id == -1) listview_scroll_by(v, step); return 0; } void listview_init_ui(listview *view) { view->id = fb_generate_item_id(); view->parent = &DEFAULT_FB_PARENT; view->level = LEVEL_LISTVIEW; view->type = FB_IT_LISTVIEW; view->keyact_item_selected = -1; view->touch.id = -1; view->tracker = touch_tracker_create(); view->last_rendered_pos.x = view->x; view->last_rendered_pos.y = view->y; view->last_rendered_pos.w = view->w; view->last_rendered_pos.h = view->h; add_touch_handler(&listview_touch_handler, view); fb_ctx_add_item(view); } void listview_destroy(listview *view) { workers_remove(listview_bounceback, view); rm_touch_handler(&listview_touch_handler, view); touch_tracker_destroy(view->tracker); listview_clear(view); list_clear(&view->ui_items, &fb_remove_item); fb_rm_rect(view->scroll_mark); fb_rm_rect(view->overscroll_marks[0]); fb_rm_rect(view->overscroll_marks[1]); fb_rm_rect(view->scroll_line); fb_ctx_rm_item(view); free(view); } listview_item *listview_add_item(listview *view, int id, void *data) { listview_item *it = mzalloc(sizeof(listview_item)); it->id = id; it->data = data; it->flags = 0; it->parent_rect = (fb_item_pos*)view; if(!view->items) keyaction_add(view, listview_keyaction_call, view); list_add(&view->items, it); return it; } void listview_clear(listview *view) { if(listview_select_item(view, NULL)) listview_update_ui(view); list_clear(&view->items, view->item_destroy); keyaction_remove(listview_keyaction_call, view); } void listview_update_ui_args(listview *view, int only_if_moved, int mutex_locked) { int y = 0; int i, it_h, visible; listview_item *it; if(only_if_moved) { if (view->x == view->last_rendered_pos.x && view->y == view->last_rendered_pos.y && view->w == view->last_rendered_pos.w && view->h == view->last_rendered_pos.h) { return; } if(view->scroll_mark) { view->scroll_mark->x += view->x - view->last_rendered_pos.x; view->scroll_mark->y += view->y - view->last_rendered_pos.y; view->scroll_line->x += view->x - view->last_rendered_pos.x; view->scroll_line->y += view->y - view->last_rendered_pos.y; view->overscroll_marks[0]->y += view->y - view->last_rendered_pos.y; view->overscroll_marks[1]->y += view->y - view->last_rendered_pos.y; } view->last_rendered_pos.x = view->x; view->last_rendered_pos.y = view->y; view->last_rendered_pos.w = view->w; view->last_rendered_pos.h = view->h; } if(!mutex_locked) fb_batch_start(); for(i = 0; view->items && view->items[i]; ++i) { it = view->items[i]; it_h = (*view->item_height)(it); visible = (int)(view->pos <= y+it_h && y-view->pos <= view->h); if(visible || (it->flags & IT_VISIBLE)) (*view->item_draw)(view->x, view->y+y-view->pos, view->w - PADDING, it); if(visible) it->flags |= IT_VISIBLE; else it->flags &= ~(IT_VISIBLE); y += it_h; } view->fullH = y; listview_enable_scroll(view, (int)(y > view->h)); if(y > view->h) listview_update_scroll_mark(view); if(!mutex_locked) fb_batch_end(); fb_request_draw(); } void listview_update_ui(listview *view) { listview_update_ui_args(view, 0, 0); } void listview_enable_scroll(listview *view, int enable) { if((view->scroll_mark != NULL) == (enable)) return; if(enable) { int x = view->x + view->w - PADDING/2 - MARK_W/2; view->scroll_mark = fb_add_rect(x, view->y, MARK_W, MARK_H, GRAY); view->scroll_mark->parent = (fb_item_pos*)view; x = view->x + view->w - PADDING/2 - LINE_W/2; view->scroll_line = fb_add_rect(x, view->y, LINE_W, view->h, GRAY); view->scroll_line->parent = (fb_item_pos*)view; view->overscroll_marks[0] = fb_add_rect(view->x, view->y, 0, OVERSCROLL_MARK_H, C_HIGHLIGHT_BG); view->overscroll_marks[0]->parent = (fb_item_pos*)view; view->overscroll_marks[1] = fb_add_rect(view->x, view->y+view->h-OVERSCROLL_MARK_H, 0, OVERSCROLL_MARK_H, C_HIGHLIGHT_BG); view->overscroll_marks[1]->parent = (fb_item_pos*)view; workers_add(listview_bounceback, view); } else { workers_remove(listview_bounceback, view); fb_rm_rect(view->scroll_mark); fb_rm_rect(view->scroll_line); fb_rm_rect(view->overscroll_marks[0]); fb_rm_rect(view->overscroll_marks[1]); view->scroll_mark = NULL; view->scroll_line = NULL; view->overscroll_marks[0] = NULL; view->overscroll_marks[1] = NULL; } } void listview_update_scroll_mark(listview *view) { if(!view->scroll_mark) return; int pos = view->pos; if(pos < 0) pos = 0; else if(pos > view->fullH - view->h) pos = view->fullH - view->h; int pct = (pos*100)/(view->fullH-view->h); int y = view->y + ((view->h - MARK_H)*pct)/100; view->scroll_mark->y = y; } void listview_update_overscroll_mark(listview *v, int side, float overscroll) { int w = v->w * (overscroll / OVERSCROLL_H); v->overscroll_marks[side]->w = w; v->overscroll_marks[side]->x = v->x + (v->w >> 1) - (w >> 1); } int listview_touch_handler(touch_event *ev, void *data) { listview *view = (listview*)data; if(view->touch.id == -1 && (ev->changed & TCHNG_ADDED)) { if (ev->x < view->x || ev->y < view->y || ev->x > view->x+view->w || ev->y > view->y+view->h) { if(listview_select_item(view, NULL)) listview_update_ui(view); return -1; } if(ev->consumed) return -1; touch_tracker_start(view->tracker, ev); view->touch.id = ev->id; view->touch.hover = listview_item_at(view, ev->y); view->touch.fast_scroll = (ev->x > view->x + view->w - PADDING*2 && ev->x <= view->x + view->w); if(view->touch.hover) { view->touch.hover->flags |= IT_HOVER; view->touch.hover->touchX = ev->x; view->touch.hover->touchY = ev->y; } else listview_select_item(view, NULL); listview_keyaction_call(view, KEYACT_CLEAR); listview_update_ui(view); return 0; } if(view->touch.id != ev->id) return -1; if(ev->changed & TCHNG_REMOVED) { if(ev->x == -1 && ev->y == -1) { if(listview_select_item(view, NULL)) listview_update_ui(view); } else if(view->touch.hover) { if(view->selected == view->touch.hover) { if(view->item_confirmed) view->item_confirmed(view->selected); } else listview_select_item(view, view->touch.hover); view->touch.hover->flags &= ~(IT_HOVER); view->touch.hover = NULL; } touch_tracker_finish(view->tracker, ev); view->touch.id = -1; listview_update_ui(view); return 0; } if((ev->changed & TCHNG_POS)) { touch_tracker_add(view->tracker, ev); if(view->touch.hover && view->tracker->distance_abs_y > SCROLL_DIST) { view->touch.hover->flags &= ~(IT_HOVER); view->touch.hover = NULL; } if(!view->touch.hover) { if(view->touch.fast_scroll) listview_scroll_to(view, ((ev->y-view->y)*100)/(view->h)); else listview_scroll_by(view, view->tracker->prev_y - ev->y); } } return 0; } int listview_select_item(listview *view, listview_item *it) { if(view->selected == it) return 0; if(view->item_selected) (*view->item_selected)(view->selected, it); if(view->selected) view->selected->flags &= ~(IT_SELECTED); if(it) it->flags |= IT_SELECTED; view->selected = it; return 1; } void listview_scroll_by(listview *view, int y) { if(!y || !view->scroll_mark) return; view->pos += y; if(view->pos < -OVERSCROLL_H) view->pos = -OVERSCROLL_H; else if(view->pos > (view->fullH - view->h) + OVERSCROLL_H) view->pos = (view->fullH - view->h) + OVERSCROLL_H; listview_select_item(view, NULL); listview_update_ui(view); } void listview_scroll_to(listview *view, int pct) { if(!view->scroll_mark) return; view->pos = ((view->fullH - view->h)*pct)/100; if(view->pos < 0) view->pos = 0; else if(view->pos > (view->fullH - view->h)) view->pos = (view->fullH - view->h); listview_select_item(view, NULL); listview_update_ui(view); } int listview_ensure_visible(listview *view, listview_item *it) { if(!view->scroll_mark) return 0; int i; int y = 0; for(i = 0; view->items[i]; ++i) { if(it == view->items[i]) break; y += view->item_height(view->items[i]); } int last_h = view->items[i] ? view->item_height(view->items[i]) : 0; if((y + last_h) - view->pos > view->h) view->pos = (y + last_h) - view->h; else if(y - view->pos < 0) view->pos = y; else return 0; return 1; } int listview_ensure_selected_visible(listview *view) { if(view->selected) return listview_ensure_visible(view, view->selected); else return 0; } listview_item *listview_item_at(listview *view, int y_pos) { int y = -view->pos + view->y; int i, it_h; listview_item *it; for(i = 0; view->items && view->items[i]; ++i) { it = view->items[i]; it_h = (*view->item_height)(it); if(y < y_pos && y+it_h > y_pos) return it; y += it_h; } return NULL; } int listview_keyaction_call(void *data, int act) { listview *v = data; switch(act) { case KEYACT_DOWN: { ++v->keyact_item_selected; if(v->keyact_item_selected >= list_item_count(v->items)) v->keyact_item_selected = -1; listview_update_keyact_frame(v); return (v->keyact_item_selected == -1) ? 1 :0; } case KEYACT_UP: { if(v->keyact_item_selected == -1) v->keyact_item_selected = list_item_count(v->items)-1; else --v->keyact_item_selected; listview_update_keyact_frame(v); return (v->keyact_item_selected == -1) ? 1 :0; } case KEYACT_CLEAR: { if(v->keyact_item_selected != -1) { v->keyact_item_selected = -1; listview_select_item(v, NULL); listview_update_ui(v); fb_request_draw(); } return 0; } case KEYACT_CONFIRM: { if(v->item_confirmed) v->item_confirmed(v->items[v->keyact_item_selected]); return 0; } default: return 0; } } void listview_update_keyact_frame(listview *view) { if(view->keyact_item_selected == -1) { if(view->selected) { listview_select_item(view, NULL); listview_update_ui(view); } return; } listview_item *it = view->items[view->keyact_item_selected]; listview_ensure_visible(view, it); int i; int y = view->y; for(i = 0; i < view->keyact_item_selected && view->items[i]; ++i) y += view->item_height(view->items[i]); int h = view->item_height(view->items[i]); y -= view->pos; listview_select_item(view, it); listview_update_ui(view); } #define ROM_ITEM_H (110*DPI_MUL) #define ROM_ITEM_SHADOW (7*DPI_MUL) #define ROM_ITEM_SEL_W (8*DPI_MUL) #define ROM_ICON_H (70*DPI_MUL) #define ROM_TEXT_PADDING_L (120*DPI_MUL) #define ROM_TEXT_PADDING_R ((ROM_TEXT_PADDING_L - ROM_ICON_H)/2) #define ROM_ICON_PADDING (ROM_TEXT_PADDING_L/2 - ROM_ICON_H/2) typedef struct { char *text; char *partition; char *icon_path; fb_text *text_it; fb_text *part_it; fb_rect *sel_rect; fb_rect *sel_rect_sh; fb_img *icon; int deselect_anim_started; int rom_name_size; int last_y; int last_x; } rom_item_data; void *rom_item_create(const char *text, const char *partition, const char *icon) { rom_item_data *data = mzalloc(sizeof(rom_item_data)); data->rom_name_size = SIZE_BIG; data->text = strdup(text); if(partition) data->partition = strdup(partition); if(icon) data->icon_path = strdup(icon); return data; } static void rom_item_deselect_finished(void *data) { rom_item_data *d = data; fb_rm_rect(d->sel_rect); fb_rm_rect(d->sel_rect_sh); d->sel_rect = NULL; d->sel_rect_sh = NULL; } static void rom_item_sel_step(void *data, UNUSED float interpolated) { rom_item_data *d = data; if(!d->sel_rect || !d->sel_rect_sh) return; d->sel_rect_sh->x = d->sel_rect->x + ROM_ITEM_SHADOW; d->sel_rect_sh->y = d->sel_rect->y + ROM_ITEM_SHADOW; d->sel_rect_sh->w = d->sel_rect->w; d->sel_rect_sh->h = d->sel_rect->h; } static void rom_item_select(int x, int y, int w, int item_h, listview_item *it, rom_item_data *d) { int baseX = it->touchX; int baseY = it->touchY; if(!baseX && !baseY) { baseX = x + w/2; baseY = y + item_h/2; } d->deselect_anim_started = 0; d->sel_rect_sh = fb_add_rect(baseX+ROM_ITEM_SHADOW, baseY+ROM_ITEM_SHADOW, 1, 1, C_BTN_FAKE_SHADOW); d->sel_rect_sh->parent = it->parent_rect; d->sel_rect = fb_add_rect(baseX, baseY, 1, 1, C_ROM_HIGHLIGHT); d->sel_rect->parent = it->parent_rect; item_anim *anim = item_anim_create(d->sel_rect, 300, INTERPOLATOR_ACCEL_DECEL); anim->start_offset = 0; anim->targetX = x; anim->targetY = y; anim->targetW = w; anim->targetH = item_h; anim->on_step_data = d; anim->on_step_call = rom_item_sel_step; item_anim_add(anim); ncard_builder *b = ncard_create_builder(); ncard_set_text(b, "Tap again to boot the system"); fb_item_pos p; p.y = y; p.h = item_h; ncard_avoid_item(b, &p); ncard_show(b, 1); } static void rom_item_deselect(int x, int y, int w, int item_h, listview_item *it, rom_item_data *d) { d->deselect_anim_started = 1; if(!((listview*)it->parent_rect)->selected) ncard_hide(); item_anim *anim = item_anim_create(d->sel_rect, 150, INTERPOLATOR_ACCELERATE); if(it->touchX || it->touchY) { anim->targetX = it->touchX; anim->targetY = it->touchY; } else { anim->targetX = x + w/2; anim->targetY = y + item_h/2; } anim->targetW = 0; anim->targetH = 0; anim->on_step_data = d; anim->on_step_call = rom_item_sel_step; anim->on_finished_data = d; anim->on_finished_call = rom_item_deselect_finished; item_anim_add_after(anim); } void rom_item_draw(int x, int y, int w, listview_item *it) { rom_item_data *d = (rom_item_data*)it->data; const int item_h = rom_item_height(it); if(!d->text_it) { d->last_x = x; d->last_y = y; fb_text_proto *p = fb_text_create(x+ROM_TEXT_PADDING_L, 0, C_TEXT, d->rom_name_size, d->text); p->style = STYLE_CONDENSED; d->text_it = fb_text_finalize(p); d->text_it->parent = it->parent_rect; while((d->text_it->w + ROM_TEXT_PADDING_L) >= (w - ROM_TEXT_PADDING_R) && d->rom_name_size > 3) fb_text_set_size(d->text_it, --d->rom_name_size); if(d->icon_path) { d->icon = fb_add_png_img(x+ROM_ICON_PADDING, 0, ROM_ICON_H, ROM_ICON_H, d->icon_path); d->icon->parent = it->parent_rect; } if(d->partition) { d->part_it = fb_add_text(x+ROM_TEXT_PADDING_L, 0, C_TEXT_SECONDARY, SIZE_SMALL, d->partition); d->part_it->parent = it->parent_rect; } } if(!d->part_it) center_text(d->text_it, -1, y, -1, item_h); else { d->text_it->y = y + (item_h/2 - (d->text_it->h + d->part_it->h + 4*DPI_MUL)/2); d->part_it->y = d->text_it->y + d->text_it->h + 4*DPI_MUL; d->part_it->x += x - d->last_x; } d->text_it->x += x - d->last_x; if(d->icon) { d->icon->x += x - d->last_x; d->icon->y = y + (item_h/2 - ROM_ICON_H/2); } if(it->flags & IT_SELECTED) { if(!d->sel_rect) { rom_item_select(x, y, w, item_h, it, d); } else { d->sel_rect_sh->x += x - d->last_x; d->sel_rect->x += x - d->last_x; d->sel_rect_sh->y += y - d->last_y; d->sel_rect->y += y - d->last_y; } } else if(d->sel_rect) { if(!d->deselect_anim_started) { rom_item_deselect(x, y, w, item_h, it, d); } else { d->sel_rect_sh->x += x - d->last_x; d->sel_rect->x += x - d->last_x; d->sel_rect_sh->y += y - d->last_y; d->sel_rect->y += y - d->last_y; } } d->last_x = x; d->last_y = y; } void rom_item_hide(void *data) { rom_item_data *d = (rom_item_data*)data; if(!d->text_it) return; fb_rm_text(d->text_it); fb_rm_text(d->part_it); fb_rm_rect(d->sel_rect); fb_rm_rect(d->sel_rect_sh); fb_rm_img(d->icon); d->text_it = NULL; d->part_it = NULL; d->sel_rect = NULL; d->sel_rect_sh = NULL; d->icon = NULL; } int rom_item_height(UNUSED listview_item *it) { return ROM_ITEM_H; } void rom_item_destroy(listview_item *it) { rom_item_hide(it->data); rom_item_data *d = (rom_item_data*)it->data; free(d->text); free(d->partition); free(d->icon_path); free(it->data); free(it); }