Skip to content

Commit

Permalink
Implement a list box ui component in preparation for the campaign man…
Browse files Browse the repository at this point in the history
…ager

There might be some issues, so as usual, testing is needed
  • Loading branch information
crudelios committed Mar 9, 2024
1 parent 5545b9f commit 5415dbf
Show file tree
Hide file tree
Showing 6 changed files with 557 additions and 415 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ set(GRAPHICS_FILES
${PROJECT_SOURCE_DIR}/src/graphics/image.c
${PROJECT_SOURCE_DIR}/src/graphics/image_button.c
${PROJECT_SOURCE_DIR}/src/graphics/lang_text.c
${PROJECT_SOURCE_DIR}/src/graphics/list_box.c
${PROJECT_SOURCE_DIR}/src/graphics/menu.c
${PROJECT_SOURCE_DIR}/src/graphics/panel.c
${PROJECT_SOURCE_DIR}/src/graphics/rich_text.c
Expand Down
266 changes: 266 additions & 0 deletions src/graphics/list_box.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#include "list_box.h"

#include "core/direction.h"
#include "graphics/button.h"
#include "graphics/font.h"
#include "graphics/panel.h"
#include "graphics/text.h"
#include "graphics/window.h"
#include "input/scroll.h"


void list_box_init(list_box_type *list_box, int total_items)
{
list_box->selected_index = LIST_BOX_NO_SELECTION;
list_box->total_items = total_items;
list_box->focus_button_id = LIST_BOX_NO_SELECTION;

scrollbar_init(&list_box->scrollbar, 0, list_box->total_items);
}

void list_box_update_total_items(list_box_type *list_box, int total_items)
{
list_box->total_items = total_items;
scrollbar_update_total_elements(&list_box->scrollbar, total_items);
list_box_request_refresh(list_box);
}

int list_box_get_total_items(const list_box_type *list_box)
{
return list_box->total_items;
}

void list_box_select_index(list_box_type *list_box, int index)
{
if (index == list_box->selected_index) {
return;
}
list_box_request_refresh(list_box);
list_box->selected_index = index;
if (list_box->on_select) {
list_box->on_select(index, 0);
}
}

int list_box_get_selected_index(const list_box_type *list_box)
{
return list_box->selected_index;
}

void list_box_show_index(list_box_type *list_box, int index)
{
if (index == LIST_BOX_NO_SELECTION) {
return;
}
scrollbar_type *scrollbar = &list_box->scrollbar;
if (index >= list_box->total_items) {
scrollbar_reset(scrollbar, list_box->total_items - scrollbar->elements_in_view + 1);
} else {
scrollbar_reset(scrollbar, index);
}
}

void list_box_show_selected_index(list_box_type *list_box)
{
list_box_show_index(list_box, list_box->selected_index);
}

int list_box_get_scroll_position(const list_box_type *list_box)
{
return list_box->scrollbar.scroll_position;
}

void list_box_request_refresh(list_box_type *list_box)
{
list_box->refresh_requested = 1;
if (!list_box->draw_inner_panel) {
window_request_refresh();
}
}

static int get_actual_width_blocks(const list_box_type *list_box)
{
int width_blocks = list_box->width_blocks;
if (!list_box->extend_to_hidden_scrollbar || list_box->total_items > list_box->scrollbar.elements_in_view) {
width_blocks -= 2;
}
return width_blocks;
}

static void draw_scrollbar(list_box_type *list_box)
{
scrollbar_type *scrollbar = &list_box->scrollbar;

scrollbar->x = list_box->x + (list_box->width_blocks - 2) * BLOCK_SIZE + 4;
scrollbar->y = list_box->y;
scrollbar->on_scroll_callback = window_request_refresh;
scrollbar->has_y_margin = 1;
scrollbar->dot_padding = list_box->decorate_scrollbar ? 8 : 0;

scrollbar->height = list_box->height_blocks * BLOCK_SIZE;
int scrollable_height_pixels = scrollbar->height;
scrollbar->scrollable_width = (list_box->width_blocks - 2) * BLOCK_SIZE;
if (list_box->draw_inner_panel) {
scrollable_height_pixels -= BLOCK_SIZE;;
}
scrollbar->elements_in_view = scrollable_height_pixels / list_box->item_height;

scrollbar_update_total_elements(scrollbar, list_box->total_items);

if (list_box->decorate_scrollbar && list_box->total_items > scrollbar->elements_in_view) {
inner_panel_draw(scrollbar->x + 4, scrollbar->y + 32, 2, scrollbar->height / BLOCK_SIZE - 4);
}
scrollbar_draw(&list_box->scrollbar);
}

void list_box_draw(list_box_type *list_box)
{
draw_scrollbar(list_box);

if(!list_box->refresh_requested) {
return;
}
list_box->refresh_requested = 0;

int width_blocks = get_actual_width_blocks(list_box);
int padding = 0;

if (list_box->draw_inner_panel) {
padding = BLOCK_SIZE / 2;
inner_panel_draw(list_box->x, list_box->y, width_blocks, list_box->height_blocks);
}

if (list_box->draw_item) {
list_box_item item = {
.x = list_box->x + padding,
.y = list_box->y + padding,
.width = width_blocks * BLOCK_SIZE - padding * 2,
.height = list_box->item_height
};

int elements_in_view = list_box->scrollbar.elements_in_view;
int index = list_box->scrollbar.scroll_position;

for (int i = 0; i < elements_in_view; i++, index++) {
if (index >= list_box->total_items) {
break;
}
item.index = index;
item.button_position = i;
item.is_selected = index == list_box->selected_index;
item.is_focused = list_box->focus_button_id == i;
list_box->draw_item(&item);
item.y += list_box->item_height;
}
}
}

static int handle_arrow_keys(list_box_type *list_box, int direction)
{
int delta;
switch (direction) {
case DIR_0_TOP:
case DIR_1_TOP_RIGHT:
case DIR_7_TOP_LEFT:
delta = -1;
break;
case DIR_4_BOTTOM:
case DIR_3_BOTTOM_RIGHT:
case DIR_5_BOTTOM_LEFT:
delta = 1;
break;
default:
return 0;
}
int max_index = list_box->total_items - 1;
if (list_box->selected_index == LIST_BOX_NO_SELECTION) {
if (delta == 1) {
list_box->selected_index = 0;
} else {
list_box->selected_index = max_index;
}
} else {
list_box->selected_index += delta;
if (list_box->selected_index == LIST_BOX_NO_SELECTION) {
list_box->selected_index = max_index;
} else if (list_box->selected_index > max_index) {
list_box->selected_index = 0;
}
}
list_box_request_refresh(list_box);
if (list_box->on_select) {
list_box->on_select(list_box->selected_index, 0);
}
scrollbar_type *scrollbar = &list_box->scrollbar;
if (list_box->selected_index > scrollbar->scroll_position + scrollbar->elements_in_view - 1) {
scrollbar_reset(scrollbar, list_box->selected_index - scrollbar->elements_in_view + 1);
} else if (list_box->selected_index < scrollbar->scroll_position) {
scrollbar_reset(scrollbar, list_box->selected_index);
}
return 1;
}

static int get_button_id_from_position(const list_box_type *list_box, int x, int y)
{
int padding = list_box->draw_inner_panel ? BLOCK_SIZE / 2 : 0;
int width_blocks = get_actual_width_blocks(list_box);
if (x < list_box->x + padding || x > list_box->x + width_blocks * BLOCK_SIZE - padding || y < list_box->y) {
return LIST_BOX_NO_SELECTION;
}
int button_id = (y - padding / 2 - list_box->y) / list_box->item_height;
if (button_id < 0 || button_id >= list_box->scrollbar.elements_in_view ||
button_id + list_box->scrollbar.scroll_position >= list_box->total_items) {
return LIST_BOX_NO_SELECTION;
}
return button_id;
}

int list_box_handle_input(list_box_type *list_box, const mouse *m, int in_dialog)
{
scrollbar_type *scrollbar = &list_box->scrollbar;

if (scrollbar_handle_mouse(scrollbar, m, in_dialog) ||
handle_arrow_keys(list_box, scroll_for_menu(m))) {
list_box_request_refresh(list_box);
return 1;
}
int old_focus_button_id = list_box->focus_button_id;
list_box->focus_button_id = get_button_id_from_position(list_box, m->x, m->y);

if (old_focus_button_id != list_box->focus_button_id) {
list_box_request_refresh(list_box);
}

if (!m->left.went_up || list_box->focus_button_id == LIST_BOX_NO_SELECTION) {
return 0;
}

if (list_box->selected_index != list_box->focus_button_id + scrollbar->scroll_position) {
list_box->selected_index = list_box->focus_button_id + scrollbar->scroll_position;
list_box_request_refresh(list_box);
}
if (list_box->on_select) {
list_box->on_select(list_box->selected_index, m->left.double_click);
}

return 1;
}

void list_box_handle_tooltip(const list_box_type *list_box, tooltip_context *c)
{
if (list_box->focus_button_id == LIST_BOX_NO_SELECTION || !list_box->handle_tooltip) {
return;
}
int padding = list_box->draw_inner_panel ? BLOCK_SIZE / 2 : 0;
list_box_item item = {
.x = list_box->x + padding,
.y = list_box->y + padding,
.width = get_actual_width_blocks(list_box) * BLOCK_SIZE - padding * 2,
.height = list_box->item_height,
.index = list_box->focus_button_id + list_box->scrollbar.scroll_position,
.button_position = list_box->focus_button_id,
.is_selected = item.index == list_box->selected_index,
.is_focused = 1
};
list_box->handle_tooltip(&item, c);
}
55 changes: 55 additions & 0 deletions src/graphics/list_box.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef LIST_BOX_H
#define LIST_BOX_H

#include "graphics/scrollbar.h"
#include "graphics/tooltip.h"
#include "input/mouse.h"

#define LIST_BOX_NO_SELECTION -1

typedef struct {
int x;
int y;
int width;
int height;
int index;
int button_position;
int is_selected;
int is_focused;
} list_box_item;

typedef struct {
int x;
int y;
int width_blocks;
int height_blocks;
int item_height;
int draw_inner_panel;
int extend_to_hidden_scrollbar;
int decorate_scrollbar;
void (*draw_item)(const list_box_item *item);
void (*on_select)(int index, int is_double_click);
void (*handle_tooltip)(const list_box_item *item, tooltip_context *c);

/* Private elements */
int total_items;
int selected_index;
scrollbar_type scrollbar;
int focus_button_id;
int refresh_requested;
} list_box_type;

void list_box_init(list_box_type *list_box, int total_items);
void list_box_update_total_items(list_box_type *list_box, int total_items);
int list_box_get_total_items(const list_box_type *list_box);
void list_box_draw(list_box_type *list_box);
int list_box_handle_input(list_box_type *list_box, const mouse *m, int in_dialog);
void list_box_handle_tooltip(const list_box_type *list_box, tooltip_context *c);
void list_box_show_index(list_box_type *list_box, int index);
void list_box_show_selected_index(list_box_type *list_box);
void list_box_select_index(list_box_type *list_box, int index);
int list_box_get_selected_index(const list_box_type *list_box);
void list_box_request_refresh(list_box_type *list_box);
int list_box_get_scroll_position(const list_box_type *list_box);

#endif // LIST_BOX_H
Loading

0 comments on commit 5415dbf

Please sign in to comment.