Steven Smith 1b78bebd94 General cleanup.
* Maintain list position after operations.
* Show current target info in batch operations.
* Fix freeze when suspending to the home menu during a threaded task.
* Other miscellaneous fixes and clean-ups.
2016-04-26 19:30:00 -07:00

289 lines
9.6 KiB
C

#include <malloc.h>
#include <3ds.h>
#include "error.h"
#include "list.h"
#include "ui.h"
#include "../core/screen.h"
#include "../core/linkedlist.h"
typedef struct {
void* data;
linked_list items;
u32 selectedIndex;
u32 selectionScroll;
u64 nextSelectionScrollResetTime;
float scrollPos;
u32 lastScrollTouchY;
u64 nextActionTime;
void (*update)(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched);
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected);
} list_data;
static float list_get_item_screen_y(list_data* listData, u32 index) {
float y = -listData->scrollPos;
linked_list_iter iter;
linked_list_iterate(&listData->items, &iter);
int i = 0;
while(linked_list_iter_has_next(&iter) && i < index) {
list_item* item = (list_item*) linked_list_iter_next(&iter);
float stringHeight;
screen_get_string_size(NULL, &stringHeight, item->name, 0.5f, 0.5f);
y += stringHeight;
i++;
}
return y;
}
static int list_get_item_at(list_data* listData, float screenY) {
float y = -listData->scrollPos;
linked_list_iter iter;
linked_list_iterate(&listData->items, &iter);
int i = 0;
while(linked_list_iter_has_next(&iter)) {
list_item* item = (list_item*) linked_list_iter_next(&iter);
float stringHeight;
screen_get_string_size(NULL, &stringHeight, item->name, 0.5f, 0.5f);
if(screenY >= y && screenY < y + stringHeight) {
return i;
}
y += stringHeight;
i++;
}
return -1;
}
static void list_validate_pos(list_data* listData, float by1, float by2) {
u32 size = linked_list_size(&listData->items);
if(size == 0 || listData->selectedIndex < 0) {
listData->selectedIndex = 0;
listData->scrollPos = 0;
}
if(size > 0) {
if(listData->selectedIndex > size - 1) {
listData->selectedIndex = size - 1;
listData->scrollPos = 0;
}
float lastItemHeight;
screen_get_string_size(NULL, &lastItemHeight, ((list_item*) linked_list_get(&listData->items, size - 1))->name, 0.5f, 0.5f);
float lastPageEnd = list_get_item_screen_y(listData, size - 1);
if(lastPageEnd < by2 - by1 - lastItemHeight) {
listData->scrollPos -= (by2 - by1 - lastItemHeight) - lastPageEnd;
}
if(listData->scrollPos < 0) {
listData->scrollPos = 0;
}
}
}
static void list_update(ui_view* view, void* data, float bx1, float by1, float bx2, float by2) {
list_data* listData = (list_data*) data;
u32 size = linked_list_size(&listData->items);
bool selectedTouched = false;
if(size > 0) {
list_validate_pos(listData, by1, by2);
float itemWidth;
screen_get_string_size(&itemWidth, NULL, ((list_item*) linked_list_get(&listData->items, listData->selectedIndex))->name, 0.5f, 0.5f);
if(itemWidth > bx2 - bx1) {
if(listData->selectionScroll == 0 || listData->selectionScroll >= itemWidth - (bx2 - bx1)) {
if(listData->nextSelectionScrollResetTime == 0) {
listData->nextSelectionScrollResetTime = osGetTime() + 2000;
} else if(osGetTime() >= listData->nextSelectionScrollResetTime) {
listData->selectionScroll = listData->selectionScroll == 0 ? 1 : 0;
listData->nextSelectionScrollResetTime = 0;
}
} else {
listData->selectionScroll++;
}
} else {
listData->selectionScroll = 0;
listData->nextSelectionScrollResetTime = 0;
}
u32 lastSelectedIndex = listData->selectedIndex;
if(((hidKeysDown() & KEY_DOWN) || ((hidKeysHeld() & KEY_DOWN) && osGetTime() >= listData->nextActionTime)) && listData->selectedIndex < size - 1) {
listData->selectedIndex++;
listData->nextActionTime = osGetTime() + ((hidKeysDown() & KEY_DOWN) ? 500 : 100);
}
if(((hidKeysDown() & KEY_UP) || ((hidKeysHeld() & KEY_UP) && osGetTime() >= listData->nextActionTime)) && listData->selectedIndex > 0) {
listData->selectedIndex--;
listData->nextActionTime = osGetTime() + ((hidKeysDown() & KEY_UP) ? 500 : 100);
}
if(((hidKeysDown() & KEY_RIGHT) || ((hidKeysHeld() & KEY_RIGHT) && osGetTime() >= listData->nextActionTime)) && listData->selectedIndex < size - 1) {
u32 remaining = size - 1 - listData->selectedIndex;
listData->selectedIndex += remaining < 13 ? remaining : 13;
listData->nextActionTime = osGetTime() + ((hidKeysDown() & KEY_RIGHT) ? 500 : 100);
}
if(((hidKeysDown() & KEY_LEFT) || ((hidKeysHeld() & KEY_LEFT) && osGetTime() >= listData->nextActionTime)) && listData->selectedIndex > 0) {
u32 remaining = listData->selectedIndex;
listData->selectedIndex -= remaining < 13 ? remaining : 13;
listData->nextActionTime = osGetTime() + ((hidKeysDown() & KEY_LEFT) ? 500 : 100);
}
if(lastSelectedIndex != listData->selectedIndex) {
listData->selectionScroll = 0;
listData->nextSelectionScrollResetTime = 0;
float itemHeight;
screen_get_string_size(NULL, &itemHeight, ((list_item*) linked_list_get(&listData->items, listData->selectedIndex))->name, 0.5f, 0.5f);
float itemY = list_get_item_screen_y(listData, listData->selectedIndex);
if(itemY + itemHeight > by2 - by1) {
listData->scrollPos -= (by2 - by1) - itemY - itemHeight;
}
if(itemY < 0) {
listData->scrollPos += itemY;
}
}
if(hidKeysDown() & KEY_TOUCH) {
touchPosition pos;
hidTouchRead(&pos);
listData->lastScrollTouchY = pos.py;
int index = list_get_item_at(listData, pos.py - by1);
if(index >= 0) {
if(listData->selectedIndex == index) {
selectedTouched = true;
} else {
listData->selectedIndex = (u32) index;
}
}
} else if(hidKeysHeld() & KEY_TOUCH) {
touchPosition pos;
hidTouchRead(&pos);
listData->scrollPos += -((int) pos.py - (int) listData->lastScrollTouchY);
listData->lastScrollTouchY = pos.py;
}
list_validate_pos(listData, by1, by2);
}
if(listData->update != NULL) {
listData->update(view, listData->data, &listData->items, linked_list_get(&listData->items, listData->selectedIndex), selectedTouched);
}
}
static void list_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
list_data* listData = (list_data*) data;
if(listData->drawTop != NULL) {
listData->drawTop(view, listData->data, x1, y1, x2, y2, linked_list_get(&listData->items, listData->selectedIndex));
}
}
static void list_draw_bottom(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
list_data* listData = (list_data*) data;
list_validate_pos(listData, y1, y2);
float y = y1 - listData->scrollPos;
linked_list_iter iter;
linked_list_iterate(&listData->items, &iter);
u32 i = 0;
while(linked_list_iter_has_next(&iter)) {
if(y > y2) {
break;
}
list_item* item = linked_list_iter_next(&iter);
float stringHeight;
screen_get_string_size(NULL, &stringHeight, item->name, 0.5f, 0.5f);
if(y > y1 - stringHeight) {
float x = x1 + 2;
if(i == listData->selectedIndex) {
x -= listData->selectionScroll;
}
screen_draw_string(item->name, x, y, 0.5f, 0.5f, item->color, false);
if(i == listData->selectedIndex) {
u32 selectionOverlayWidth = 0;
u32 selectionOverlayHeight = 0;
screen_get_texture_size(&selectionOverlayWidth, &selectionOverlayHeight, TEXTURE_SELECTION_OVERLAY);
screen_draw_texture(TEXTURE_SELECTION_OVERLAY, (x1 + x2 - selectionOverlayWidth) / 2, y, selectionOverlayWidth, stringHeight);
}
}
y += stringHeight;
i++;
}
}
void list_display(const char* name, const char* info, void* data, void (*update)(ui_view* view, void* data, linked_list* items, list_item* selected, bool selectedTouched),
void (*drawTop)(ui_view* view, void* data, float x1, float y1, float x2, float y2, list_item* selected)) {
list_data* listData = (list_data*) calloc(1, sizeof(list_data));
if(listData == NULL) {
error_display(NULL, NULL, NULL, "Failed to allocate list data.");
return;
}
listData->data = data;
linked_list_init(&listData->items);
listData->selectedIndex = 0;
listData->selectionScroll = 0;
listData->nextSelectionScrollResetTime = 0;
listData->scrollPos = 0;
listData->lastScrollTouchY = 0;
listData->update = update;
listData->drawTop = drawTop;
ui_view* view = (ui_view*) calloc(1, sizeof(ui_view));
if(view == NULL) {
error_display(NULL, NULL, NULL, "Failed to allocate UI view.");
free(listData);
return;
}
view->name = name;
view->info = info;
view->data = listData;
view->update = list_update;
view->drawTop = list_draw_top;
view->drawBottom = list_draw_bottom;
ui_push(view);
}
void list_destroy(ui_view* view) {
linked_list_destroy(&((list_data*) view->data)->items);
free(view->data);
free(view);
}