mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-29 13:03:40 +01:00
jlist, jscrolledlist: add versatile paintable list widgets
This commit is contained in:
parent
51fbf2f582
commit
beeb0c0724
8 changed files with 597 additions and 23 deletions
|
@ -30,6 +30,8 @@ add_library(${NAME} STATIC
|
||||||
src/jfkeys.c
|
src/jfkeys.c
|
||||||
src/jfileselect.c
|
src/jfileselect.c
|
||||||
src/jframe.c
|
src/jframe.c
|
||||||
|
src/jlist.c
|
||||||
|
src/jscrolledlist.c
|
||||||
src/vec.c
|
src/vec.c
|
||||||
src/keymap.c
|
src/keymap.c
|
||||||
${ASSETS_${FXSDK_PLATFORM}}
|
${ASSETS_${FXSDK_PLATFORM}}
|
||||||
|
|
|
@ -21,6 +21,12 @@ typedef struct {
|
||||||
uint8_t left;
|
uint8_t left;
|
||||||
} jdirs;
|
} jdirs;
|
||||||
|
|
||||||
|
/* jrect: Small rectangle */
|
||||||
|
typedef struct {
|
||||||
|
int16_t x, y;
|
||||||
|
int16_t w, h;
|
||||||
|
} jrect;
|
||||||
|
|
||||||
/* jalign: Alignment options with both horizontal and vertical names */
|
/* jalign: Alignment options with both horizontal and vertical names */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
/* Horizontal names */
|
/* Horizontal names */
|
||||||
|
|
|
@ -39,6 +39,9 @@ typedef struct {
|
||||||
/* If floating_scrollbars == false, spacing between scrollbars and child */
|
/* If floating_scrollbars == false, spacing between scrollbars and child */
|
||||||
uint8_t scrollbar_spacing;
|
uint8_t scrollbar_spacing;
|
||||||
|
|
||||||
|
/* Visibility margin (see jframe_scroll_to_region()) */
|
||||||
|
uint8_t visibility_margin_x, visibility_margin_y;
|
||||||
|
|
||||||
/* Whether scrollbars are shown */
|
/* Whether scrollbars are shown */
|
||||||
bool scrollbar_x, scrollbar_y;
|
bool scrollbar_x, scrollbar_y;
|
||||||
/* Current scroll offsets */
|
/* Current scroll offsets */
|
||||||
|
@ -56,10 +59,29 @@ typedef struct {
|
||||||
jframe *jframe_create(void *parent);
|
jframe *jframe_create(void *parent);
|
||||||
|
|
||||||
/* Trivial properties */
|
/* Trivial properties */
|
||||||
void jframe_set_align(jframe *j, jalign halign, jalign valign);
|
void jframe_set_align(jframe *f, jalign halign, jalign valign);
|
||||||
void jframe_set_scrollbars_always_visible(jframe *j, bool always_visible);
|
void jframe_set_scrollbars_always_visible(jframe *f, bool always_visible);
|
||||||
void jframe_set_floating_scrollbars(jframe *j, bool floating_scrollbars);
|
void jframe_set_floating_scrollbars(jframe *f, bool floating_scrollbars);
|
||||||
void jframe_set_keyboard_control(jframe *j, bool keyboard_control);
|
void jframe_set_keyboard_control(jframe *f, bool keyboard_control);
|
||||||
void jframe_set_match_size(jframe *j, bool match_width, bool match_height);
|
void jframe_set_match_size(jframe *f, bool match_width, bool match_height);
|
||||||
|
void jframe_set_visibility_margin(jframe *f, int margin_x, int margin_y);
|
||||||
|
|
||||||
|
/* jframe_scroll_to_region(): Scroll a region of the child into view
|
||||||
|
|
||||||
|
This functions scrolls the frame to ensure that the specified region of the
|
||||||
|
child widget is visible within the frame (minus the visibility margin).
|
||||||
|
|
||||||
|
The purpose of the visibility margin is to avoid aligning important regions
|
||||||
|
of the child widget along the edges of the frame unless we reach the edge of
|
||||||
|
the child widget. For example, with a scrolling list, we want the selected
|
||||||
|
item to be somewhat off the edge of the frame so that items around it are
|
||||||
|
visible. Showing the selected item right on the edge of the frame suggests
|
||||||
|
to the user that there are no items beyond it.
|
||||||
|
|
||||||
|
If either dimension of the provided region is larger than the content size
|
||||||
|
of the frame minus the visibility margin, the center of the region will be
|
||||||
|
shown at the center of the view along that direciton. Otherwise, the view
|
||||||
|
will scroll the minimum amount possible to bring the region into view. */
|
||||||
|
void jframe_scroll_to_region(jframe *f, jrect region);
|
||||||
|
|
||||||
#endif /* _J_JFRAME */
|
#endif /* _J_JFRAME */
|
||||||
|
|
102
include/justui/jlist.h
Normal file
102
include/justui/jlist.h
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
//---
|
||||||
|
// JustUI.jlist: List widget with arbitrary, selectable children
|
||||||
|
//---
|
||||||
|
|
||||||
|
#ifndef _J_JLIST
|
||||||
|
#define _J_JLIST
|
||||||
|
|
||||||
|
#include <justui/defs.h>
|
||||||
|
#include <justui/jwidget.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/* Selected item is indicated by inverting its rendered area */
|
||||||
|
JLIST_SELECTION_INVERT = 0,
|
||||||
|
/* Selected item is indicated by applying a background color */
|
||||||
|
JLIST_SELECTION_BACKGROUND = 1,
|
||||||
|
|
||||||
|
} jlist_selection_style;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* Delegate widget */
|
||||||
|
jwidget *delegate;
|
||||||
|
/* Whether item can be selected */
|
||||||
|
bool selectable;
|
||||||
|
/* Whether item can be triggered */
|
||||||
|
bool triggerable;
|
||||||
|
|
||||||
|
/* The following fields are only applicable if there is no delegate. */
|
||||||
|
|
||||||
|
/* Item's natural with and height, in pixels */
|
||||||
|
int16_t natural_width, natural_height;
|
||||||
|
|
||||||
|
} jlist_item_info;
|
||||||
|
|
||||||
|
struct jlist;
|
||||||
|
|
||||||
|
typedef void (*jlist_item_info_function)(struct jlist *list, int index,
|
||||||
|
jlist_item_info *info);
|
||||||
|
|
||||||
|
typedef void (*jlist_item_paint_function)(int x, int y, int w, int h,
|
||||||
|
struct jlist *list, int index, bool selected);
|
||||||
|
|
||||||
|
/* jlist: List widget with arbitrary, selectable children
|
||||||
|
|
||||||
|
This widget is used to make lists of selectable elements. The elements are
|
||||||
|
backed by a model which is essentially associating an index in the list to
|
||||||
|
some piece of user data.
|
||||||
|
|
||||||
|
Elements can either be manually-rendered like jpainted, or be delegated to
|
||||||
|
full widgets (eg. for editing in a list).
|
||||||
|
|
||||||
|
In terms of layout, jlist is a raw vertical list of all of its items, with
|
||||||
|
no spacing. Generally it is desirable to put it in a jframe to make it
|
||||||
|
scroll; otherwise, it has rather unpredictable dimensions. */
|
||||||
|
typedef struct jlist {
|
||||||
|
jwidget widget;
|
||||||
|
|
||||||
|
/* Number of items */
|
||||||
|
int item_count;
|
||||||
|
/* Per-widget information */
|
||||||
|
jlist_item_info *items;
|
||||||
|
/* Item information and paint functions */
|
||||||
|
jlist_item_info_function info_function;
|
||||||
|
jlist_item_paint_function paint_function;
|
||||||
|
|
||||||
|
/* Currently selected item, -1 if none */
|
||||||
|
int cursor;
|
||||||
|
|
||||||
|
} jlist;
|
||||||
|
|
||||||
|
/* Events */
|
||||||
|
extern uint16_t JLIST_ITEM_TRIGGERED;
|
||||||
|
extern uint16_t JLIST_SELECTION_MOVED;
|
||||||
|
extern uint16_t JLIST_MODEL_UPDATED;
|
||||||
|
|
||||||
|
/* jlist_create(): Create a new (empty) jlist. */
|
||||||
|
jlist *jlist_create(void *parent, jlist_item_info_function info_function,
|
||||||
|
jlist_item_paint_function paint_function);
|
||||||
|
|
||||||
|
/* jlist_update_model(): Update jlists's information about the model
|
||||||
|
The new model size is passed as parameter. The model is refreshed by
|
||||||
|
repeatedly calling the info function. */
|
||||||
|
void jlist_update_model(jlist *l, int item_count);
|
||||||
|
|
||||||
|
/* jlist_clear(): Remove all items */
|
||||||
|
void jlist_clear(jlist *l);
|
||||||
|
|
||||||
|
/* jlist_select(): Move selection to a selectable item */
|
||||||
|
void jlist_select(jlist *l, int item);
|
||||||
|
|
||||||
|
/* jlist_selected_item(): Get currently selected item (-1 if none) */
|
||||||
|
int jlist_selected_item(jlist *l);
|
||||||
|
|
||||||
|
/* jlist_selected_region(): Get the currently selected region of the widget
|
||||||
|
|
||||||
|
The region is returned as a jrect within the widget's coordinates. This is
|
||||||
|
useful when the list is inside a frame, to scroll the frame to a suitable
|
||||||
|
position after the list's selection moved. See jscrolledlist.
|
||||||
|
|
||||||
|
The returned region is undefined if there is no selected item. */
|
||||||
|
jrect jlist_selected_region(jlist *l);
|
||||||
|
|
||||||
|
#endif /* _J_JLIST */
|
33
include/justui/jscrolledlist.h
Normal file
33
include/justui/jscrolledlist.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//---
|
||||||
|
// JustUI.jscrolledlist: A jlist inside a jframe
|
||||||
|
//---
|
||||||
|
|
||||||
|
#ifndef _J_JSCROLLEDLIST
|
||||||
|
#define _J_JSCROLLEDLIST
|
||||||
|
|
||||||
|
#include <justui/defs.h>
|
||||||
|
#include <justui/jlist.h>
|
||||||
|
#include <justui/jframe.h>
|
||||||
|
|
||||||
|
/* jscrolledlist: A jlist inside a jframe
|
||||||
|
|
||||||
|
jlist as a variabled-size widget which is intended to be used inside a
|
||||||
|
scrolling view like a jframe. However this still requires the jframe to
|
||||||
|
scroll when the list cursor moves and when the model is refreshed.
|
||||||
|
|
||||||
|
This utility widget does this wrapping. It does not have any specific
|
||||||
|
functions, and instead returns the list and frame as its `->list` and
|
||||||
|
`->frame` members. */
|
||||||
|
typedef struct {
|
||||||
|
jwidget widget;
|
||||||
|
jframe *frame;
|
||||||
|
jlist *list;
|
||||||
|
|
||||||
|
} jscrolledlist;
|
||||||
|
|
||||||
|
/* jscrolledlist_create(): Create a scrolled list */
|
||||||
|
jscrolledlist *jscrolledlist_create(void *parent,
|
||||||
|
jlist_item_info_function info_function,
|
||||||
|
jlist_item_paint_function paint_function);
|
||||||
|
|
||||||
|
#endif /* _J_JSCROLLEDLIST */
|
105
src/jframe.c
105
src/jframe.c
|
@ -34,9 +34,13 @@ jframe *jframe_create(void *parent)
|
||||||
#ifdef FX9860G
|
#ifdef FX9860G
|
||||||
f->scrollbar_width = 1;
|
f->scrollbar_width = 1;
|
||||||
f->scrollbar_spacing = 1;
|
f->scrollbar_spacing = 1;
|
||||||
|
f->visibility_margin_x = 4;
|
||||||
|
f->visibility_margin_y = 4;
|
||||||
#else
|
#else
|
||||||
f->scrollbar_width = 2;
|
f->scrollbar_width = 2;
|
||||||
f->scrollbar_spacing = 2;
|
f->scrollbar_spacing = 2;
|
||||||
|
f->visibility_margin_x = 8;
|
||||||
|
f->visibility_margin_y = 8;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
f->scroll_x = 0;
|
f->scroll_x = 0;
|
||||||
|
@ -47,11 +51,11 @@ jframe *jframe_create(void *parent)
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jwidget *frame_child(jframe *j)
|
static jwidget *frame_child(jframe *f)
|
||||||
{
|
{
|
||||||
if(j->widget.child_count == 0)
|
if(f->widget.child_count == 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
return j->widget.children[0];
|
return f->widget.children[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static int frame_scrollbar_space_size(jframe *f)
|
static int frame_scrollbar_space_size(jframe *f)
|
||||||
|
@ -83,37 +87,102 @@ static int aligned_start_within(int B, int S, jalign align)
|
||||||
// Getters and setters
|
// Getters and setters
|
||||||
//---
|
//---
|
||||||
|
|
||||||
void jframe_set_align(jframe *j, jalign halign, jalign valign)
|
void jframe_set_align(jframe *f, jalign halign, jalign valign)
|
||||||
{
|
{
|
||||||
if(j->halign == halign && j->valign == valign)
|
if(f->halign == halign && f->valign == valign)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
j->halign = halign;
|
f->halign = halign;
|
||||||
j->valign = valign;
|
f->valign = valign;
|
||||||
j->widget.update = 1;
|
f->widget.update = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jframe_set_scrollbars_always_visible(jframe *j, bool always_visible)
|
void jframe_set_scrollbars_always_visible(jframe *f, bool always_visible)
|
||||||
{
|
{
|
||||||
if(j->scrollbars_always_visible == always_visible)
|
if(f->scrollbars_always_visible == always_visible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
j->scrollbars_always_visible = always_visible;
|
f->scrollbars_always_visible = always_visible;
|
||||||
j->widget.dirty = 1;
|
f->widget.dirty = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jframe_set_floating_scrollbars(jframe *j, bool floating_scrollbars)
|
void jframe_set_floating_scrollbars(jframe *f, bool floating_scrollbars)
|
||||||
{
|
{
|
||||||
if(j->floating_scrollbars == floating_scrollbars)
|
if(f->floating_scrollbars == floating_scrollbars)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
j->floating_scrollbars = floating_scrollbars;
|
f->floating_scrollbars = floating_scrollbars;
|
||||||
j->widget.dirty = 1;
|
f->widget.dirty = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jframe_set_keyboard_control(jframe *j, bool keyboard_control)
|
void jframe_set_keyboard_control(jframe *f, bool keyboard_control)
|
||||||
{
|
{
|
||||||
j->keyboard_control = keyboard_control;
|
f->keyboard_control = keyboard_control;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jframe_set_match_size(jframe *f, bool match_width, bool match_height)
|
||||||
|
{
|
||||||
|
if(f->match_width == match_width && f->match_height == match_height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
f->match_width = match_width;
|
||||||
|
f->match_height = match_height;
|
||||||
|
f->widget.dirty = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jframe_set_visibility_margin(jframe *f, int margin_x, int margin_y)
|
||||||
|
{
|
||||||
|
f->visibility_margin_x = margin_x;
|
||||||
|
f->visibility_margin_y = margin_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Scrolling
|
||||||
|
//---
|
||||||
|
|
||||||
|
void jframe_scroll_to_region(jframe *f, jrect region)
|
||||||
|
{
|
||||||
|
jwidget *child = frame_child(f);
|
||||||
|
if(!child)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int x1 = region.x, x2 = x1 + region.w;
|
||||||
|
int y1 = region.y, y2 = y1 + region.h;
|
||||||
|
|
||||||
|
/* Clipping */
|
||||||
|
x1 = max(x1, 0);
|
||||||
|
y1 = max(y1, 0);
|
||||||
|
x2 = min(x2, jwidget_full_width(child));
|
||||||
|
y2 = min(y2, jwidget_full_height(child));
|
||||||
|
|
||||||
|
/* Viewport region
|
||||||
|
TODO: Handle oversized visibility margin properly */
|
||||||
|
int vp_x1 = f->visibility_margin_x;
|
||||||
|
int vp_x2 = jwidget_content_width(f) - f->visibility_margin_x;
|
||||||
|
int vp_y1 = f->visibility_margin_y;
|
||||||
|
int vp_y2 = jwidget_content_height(f) - f->visibility_margin_y;
|
||||||
|
|
||||||
|
/* If the requested region doesn't fit in the viewport, center on it */
|
||||||
|
if(x2 - x1 > vp_x2 - vp_x1)
|
||||||
|
f->scroll_x = (x1 + x2) / 2 - (vp_x1 + vp_x2) / 2;
|
||||||
|
/* The visible region for some scroll_x is
|
||||||
|
scroll_x + vp_x1 ... scroll_x + vp_x2
|
||||||
|
The minimum/maximum value for scroll_x are when
|
||||||
|
x2 - scroll_x = vp_x2 (region x2 touches viewport x2)
|
||||||
|
x1 - scroll_x = vp_x1 (region x1 touches viewport x1)
|
||||||
|
The max is >= the min due to the guard on the if. */
|
||||||
|
else
|
||||||
|
f->scroll_x = clamp(f->scroll_x, x2 - vp_x2, x1 - vp_x1);
|
||||||
|
|
||||||
|
if(y2 - y1 > vp_y2 - vp_y1)
|
||||||
|
f->scroll_y = (y1 + y2) / 2 - (vp_y1 + vp_y2) / 2;
|
||||||
|
else
|
||||||
|
f->scroll_y = clamp(f->scroll_y, y2 - vp_y2, y1 - vp_y1);
|
||||||
|
|
||||||
|
/* Safety clamp */
|
||||||
|
f->scroll_x = clamp(f->scroll_x, 0, f->max_scroll_x);
|
||||||
|
f->scroll_y = clamp(f->scroll_y, 0, f->max_scroll_y);
|
||||||
|
f->widget.update = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
|
|
272
src/jlist.c
Normal file
272
src/jlist.c
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
#include <justui/jwidget.h>
|
||||||
|
#include <justui/jwidget-api.h>
|
||||||
|
#include <justui/jlist.h>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <gint/display.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* Type identifier for jlist */
|
||||||
|
static int jlist_type_id = -1;
|
||||||
|
|
||||||
|
/* Events */
|
||||||
|
uint16_t JLIST_ITEM_TRIGGERED;
|
||||||
|
uint16_t JLIST_SELECTION_MOVED;
|
||||||
|
uint16_t JLIST_MODEL_UPDATED;
|
||||||
|
|
||||||
|
struct jlist_item_info {
|
||||||
|
/* Whether the item can be selected */
|
||||||
|
bool selectable;
|
||||||
|
};
|
||||||
|
|
||||||
|
jlist *jlist_create(void *parent, jlist_item_info_function info_function,
|
||||||
|
jlist_item_paint_function paint_function)
|
||||||
|
{
|
||||||
|
if(jlist_type_id < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
jlist *l = malloc(sizeof *l);
|
||||||
|
if(!l)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
jwidget_init(&l->widget, jlist_type_id, parent);
|
||||||
|
|
||||||
|
l->item_count = 0;
|
||||||
|
l->items = NULL;
|
||||||
|
l->info_function = info_function;
|
||||||
|
l->paint_function = paint_function;
|
||||||
|
l->cursor = -1;
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Selection management
|
||||||
|
//---
|
||||||
|
|
||||||
|
static int prev_selectable(jlist *l, int cursor)
|
||||||
|
{
|
||||||
|
for(int i = cursor - 1; i >= 0; i--) {
|
||||||
|
if(l->items[i].selectable)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int next_selectable(jlist *l, int cursor)
|
||||||
|
{
|
||||||
|
for(int i = cursor + 1; i < l->item_count; i++) {
|
||||||
|
if(l->items[i].selectable)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int first_selectable(jlist *l)
|
||||||
|
{
|
||||||
|
return next_selectable(l, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int last_selectable(jlist *l)
|
||||||
|
{
|
||||||
|
int p = prev_selectable(l, l->item_count);
|
||||||
|
return p == l->item_count ? -1 : p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nearest_selectable(jlist *l, int cursor)
|
||||||
|
{
|
||||||
|
if(cursor < 0)
|
||||||
|
return first_selectable(l);
|
||||||
|
if(cursor >= l->item_count)
|
||||||
|
return last_selectable(l);
|
||||||
|
if(l->items[cursor].selectable)
|
||||||
|
return cursor;
|
||||||
|
|
||||||
|
int i = prev_selectable(l, cursor);
|
||||||
|
if(i != cursor)
|
||||||
|
return i;
|
||||||
|
i = next_selectable(l, cursor);
|
||||||
|
if(i != cursor)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jlist_select(jlist *l, int cursor)
|
||||||
|
{
|
||||||
|
/* Normalize out-of-bounds to -1 */
|
||||||
|
if(cursor < 0 || cursor >= l->item_count)
|
||||||
|
cursor = -1;
|
||||||
|
if(l->cursor == cursor || (cursor > 0 && !l->items[cursor].selectable))
|
||||||
|
return;
|
||||||
|
|
||||||
|
l->cursor = cursor;
|
||||||
|
jwidget_emit(l, (jevent){ .type = JLIST_SELECTION_MOVED });
|
||||||
|
l->widget.update = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int jlist_selected_item(jlist *l)
|
||||||
|
{
|
||||||
|
return l->cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Item management
|
||||||
|
//---
|
||||||
|
|
||||||
|
void jlist_update_model(jlist *l, int item_count)
|
||||||
|
{
|
||||||
|
if(l->item_count != item_count) {
|
||||||
|
l->items = realloc(l->items, item_count * sizeof *l->items);
|
||||||
|
if(!l->items) {
|
||||||
|
item_count = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l->item_count = item_count;
|
||||||
|
for(int i = 0; i < item_count; i++) {
|
||||||
|
l->info_function(l, i, &l->items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
jlist_select(l, nearest_selectable(l, l->cursor));
|
||||||
|
jwidget_emit(l, (jevent){ .type = JLIST_MODEL_UPDATED });
|
||||||
|
l->widget.dirty = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jlist_clear(jlist *l)
|
||||||
|
{
|
||||||
|
jlist_update_model(l, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
jrect jlist_selected_region(jlist *l)
|
||||||
|
{
|
||||||
|
int y=0, h=0;
|
||||||
|
|
||||||
|
for(int i = 0; i <= l->cursor; i++) {
|
||||||
|
jlist_item_info *info = &l->items[i];
|
||||||
|
y += h;
|
||||||
|
if(info->delegate)
|
||||||
|
h = jwidget_full_height(info->delegate);
|
||||||
|
else
|
||||||
|
h = info->natural_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (jrect){ .x = 0, .y = y, .w = jwidget_content_width(l), .h = h };
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Polymorphic widget operations
|
||||||
|
//---
|
||||||
|
|
||||||
|
static void jlist_poly_csize(void *l0)
|
||||||
|
{
|
||||||
|
jlist *l = l0;
|
||||||
|
jwidget *w = &l->widget;
|
||||||
|
|
||||||
|
w->w = 0;
|
||||||
|
w->h = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i < l->item_count; i++) {
|
||||||
|
jlist_item_info *info = &l->items[i];
|
||||||
|
int item_w, item_h;
|
||||||
|
|
||||||
|
if(info->delegate) {
|
||||||
|
item_w = jwidget_full_width(info->delegate);
|
||||||
|
item_h = jwidget_full_height(info->delegate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item_w = info->natural_width;
|
||||||
|
item_h = info->natural_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->w = max(w->w, item_w);
|
||||||
|
w->h += item_h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void jlist_poly_render(void *l0, int x, int y)
|
||||||
|
{
|
||||||
|
jlist *l = l0;
|
||||||
|
int x1 = x;
|
||||||
|
int x2 = x + jwidget_content_width(l) - 1;
|
||||||
|
|
||||||
|
for(int i = 0; i < l->item_count; i++) {
|
||||||
|
jlist_item_info *info = &l->items[i];
|
||||||
|
|
||||||
|
if(info->delegate) {
|
||||||
|
jwidget_render(info->delegate, x1, y);
|
||||||
|
y += jwidget_full_height(info->delegate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
l->paint_function(x1, y, x2-x1+1, info->natural_height, l, i,
|
||||||
|
l->cursor == i);
|
||||||
|
y += info->natural_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool jlist_poly_event(void *l0, jevent e)
|
||||||
|
{
|
||||||
|
jlist *l = l0;
|
||||||
|
|
||||||
|
if(e.type != JWIDGET_KEY || l->cursor < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
key_event_t ev = e.key;
|
||||||
|
if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int key = ev.key;
|
||||||
|
|
||||||
|
/* Cursor movement */
|
||||||
|
|
||||||
|
if(key == KEY_UP && ev.shift && !ev.alpha) {
|
||||||
|
jlist_select(l, first_selectable(l));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(key == KEY_DOWN && ev.shift && !ev.alpha) {
|
||||||
|
jlist_select(l, last_selectable(l));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(key == KEY_UP && !ev.alpha) {
|
||||||
|
jlist_select(l, prev_selectable(l, l->cursor));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(key == KEY_DOWN && !ev.alpha) {
|
||||||
|
jlist_select(l, next_selectable(l, l->cursor));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Triggering items */
|
||||||
|
|
||||||
|
if(key == KEY_EXE && l->items[l->cursor].triggerable) {
|
||||||
|
jevent e = { .type = JLIST_ITEM_TRIGGERED, .data = l->cursor };
|
||||||
|
jwidget_emit(l, e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void jlist_poly_destroy(void *l0)
|
||||||
|
{
|
||||||
|
jlist *l = l0;
|
||||||
|
free(l->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jlist type definition */
|
||||||
|
static jwidget_poly type_jlist = {
|
||||||
|
.name = "jlist",
|
||||||
|
.csize = jlist_poly_csize,
|
||||||
|
.render = jlist_poly_render,
|
||||||
|
.event = jlist_poly_event,
|
||||||
|
.destroy = jlist_poly_destroy,
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((constructor(1001)))
|
||||||
|
static void j_register_jlist(void)
|
||||||
|
{
|
||||||
|
jlist_type_id = j_register_widget(&type_jlist, "jwidget");
|
||||||
|
JLIST_ITEM_TRIGGERED = j_register_event();
|
||||||
|
JLIST_SELECTION_MOVED = j_register_event();
|
||||||
|
JLIST_MODEL_UPDATED = j_register_event();
|
||||||
|
}
|
68
src/jscrolledlist.c
Normal file
68
src/jscrolledlist.c
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#include <justui/jwidget.h>
|
||||||
|
#include <justui/jwidget-api.h>
|
||||||
|
#include <justui/jscrolledlist.h>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* Type identifier for jscrolledlist */
|
||||||
|
static int jscrolledlist_type_id = -1;
|
||||||
|
|
||||||
|
jscrolledlist *jscrolledlist_create(void *parent,
|
||||||
|
jlist_item_info_function info_function,
|
||||||
|
jlist_item_paint_function paint_function)
|
||||||
|
{
|
||||||
|
if(jscrolledlist_type_id < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
jscrolledlist *l = malloc(sizeof *l);
|
||||||
|
if(!l)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
jwidget_init(&l->widget, jscrolledlist_type_id, parent);
|
||||||
|
jwidget_set_stretch(l, 1, 1, false);
|
||||||
|
jlayout_set_vbox(l);
|
||||||
|
|
||||||
|
l->frame = jframe_create(l);
|
||||||
|
jwidget_set_stretch(l->frame, 1, 1, false);
|
||||||
|
jframe_set_align(l->frame, J_ALIGN_LEFT, J_ALIGN_TOP);
|
||||||
|
|
||||||
|
l->list = jlist_create(l->frame, info_function, paint_function);
|
||||||
|
jwidget_set_stretch(l->list, 1, 1, false);
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Polymorphic widget operations
|
||||||
|
//---
|
||||||
|
|
||||||
|
static bool jscrolledlist_poly_event(void *l0, jevent e)
|
||||||
|
{
|
||||||
|
jscrolledlist *l = l0;
|
||||||
|
|
||||||
|
if((e.type == JLIST_SELECTION_MOVED || e.type == JLIST_MODEL_UPDATED)
|
||||||
|
&& e.source == l->list) {
|
||||||
|
int cursor = jlist_selected_item(l->list);
|
||||||
|
if(cursor >= 0) {
|
||||||
|
jrect r = jlist_selected_region(l->list);
|
||||||
|
jframe_scroll_to_region(l->frame, r);
|
||||||
|
/* Allow the event to propagate up */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jscrolledlist type definition */
|
||||||
|
static jwidget_poly type_jscrolledlist = {
|
||||||
|
.name = "jscrolledlist",
|
||||||
|
.event = jscrolledlist_poly_event,
|
||||||
|
};
|
||||||
|
|
||||||
|
__attribute__((constructor(1002)))
|
||||||
|
static void j_register_jscrolledlist(void)
|
||||||
|
{
|
||||||
|
jscrolledlist_type_id = j_register_widget(&type_jscrolledlist, "jwidget");
|
||||||
|
}
|
Loading…
Reference in a new issue