Compare commits

...

16 Commits

Author SHA1 Message Date
Lephenixnoir d9c18117c5
bump version to 1.3.0 2023-04-10 14:19:54 +02:00
Lephenixnoir a84b0adb3a
jlist: fix jlist_clear() 2022-12-02 09:56:33 +01:00
Lephenixnoir f5d6fb2e87
jscrolledlist: hack to allow scroll before re-layout 2022-12-01 23:59:31 +01:00
Lephenixnoir 40ffe54250
jfkeys: fix key levels not working at all 2022-12-01 11:00:41 +01:00
Lephenixnoir beeb0c0724
jlist, jscrolledlist: add versatile paintable list widgets 2022-12-01 11:00:27 +01:00
Lephenixnoir 51fbf2f582
jframe: new widget that adds scrolling to a large child (basics) 2022-11-20 14:56:42 +01:00
Lephenixnoir 950c5b7152
jwidget: add a per-widget clipping setting 2022-11-19 23:42:38 +01:00
Lephenixnoir 67219834be
jscene: honor the getkey feature function in jscene_run() 2022-11-19 18:58:19 +01:00
Lephenixnoir 2e15bb8c96
jscene: fix delivery of focus change events 2022-11-19 18:57:36 +01:00
Lephenixnoir 5b591a6fb4
jinput: fix out-of-bounds bug in weird cursor positions 2022-11-19 18:53:58 +01:00
Lephenixnoir 7c0e8257f7
jevent: add generic integer data option in events 2022-11-06 19:51:28 +01:00
Lephenixnoir cfeba2695c
jfileselect: add a scrollbar option (enabled by default) 2022-11-05 20:58:34 +01:00
Lephenixnoir fd9191cc43
jfileselect: friendlier fx-9860G defaults 2022-11-05 19:06:33 +01:00
Lephenixnoir a588c24f59
jfileselect: add a programmable filter 2022-11-05 18:50:40 +01:00
Lephenixnoir 34ee43d67f
jscene: update for extra parameters in keydev_read() 2022-11-04 11:10:21 +01:00
Lephenixnoir b0195af198
jfkeys: add color and font parameters 2022-08-29 14:57:18 +02:00
19 changed files with 1230 additions and 54 deletions

View File

@ -1,7 +1,7 @@
# Just UI
cmake_minimum_required(VERSION 3.16)
project(JustUI VERSION 1.2.0 LANGUAGES C)
project(JustUI VERSION 1.3.0 LANGUAGES C)
find_package(Gint 2.8 REQUIRED)
include(Fxconv)
@ -29,6 +29,9 @@ add_library(${NAME} STATIC
src/jpainted.c
src/jfkeys.c
src/jfileselect.c
src/jframe.c
src/jlist.c
src/jscrolledlist.c
src/vec.c
src/keymap.c
${ASSETS_${FXSDK_PLATFORM}}

View File

@ -21,6 +21,12 @@ typedef struct {
uint8_t left;
} jdirs;
/* jrect: Small rectangle */
typedef struct {
int16_t x, y;
int16_t w, h;
} jrect;
/* jalign: Alignment options with both horizontal and vertical names */
typedef enum {
/* Horizontal names */

View File

@ -43,6 +43,8 @@ typedef struct {
union {
/* JWIDGET_KEY events */
key_event_t key;
/* Custom generic value */
int data;
};
} jevent;

View File

@ -39,6 +39,10 @@ typedef struct {
bool saveas;
/* Whether we are currently using the input field */
bool input_mode;
/* Scrollbar with (0 to disable) */
int8_t scrollbar_width;
/* File filter; NULL accepts everything */
bool (*filter_function)(struct dirent const *entry);
/* Current cursor position (0 .. folder_entries-1) */
int16_t cursor;
@ -56,7 +60,7 @@ typedef struct {
} jfileselect;
/* Type IDs */
/* Event IDs */
extern uint16_t JFILESELECT_LOADED;
extern uint16_t JFILESELECT_VALIDATED;
extern uint16_t JFILESELECT_CANCELED;
@ -96,9 +100,22 @@ char const *jfileselect_selected_file(jfileselect *fs);
/* jfileselect_current_folder(): Get the path to the current folder */
char const *jfileselect_current_folder(jfileselect *fs);
/* jfileselect_set_filter(): Set a filter function
The function is called on each directory entry read when scanning a folder.
It should return true to show the entry, false to ignore it. By default,
jfileselect_default_filter is used. Note filters in general should really
accept folders. */
void jfileselect_set_filter(jfileselect *fs,
bool (*filter)(struct dirent const *entry));
/* Default filter. Rejects "@MainMem", "SAVE-F", "." and "..". */
bool jfileselect_default_filter(struct dirent const *entry);
/* Trivial properties */
void jfileselect_set_font(jfileselect *fs, font_t const *font);
void jfileselect_set_line_spacing(jfileselect *fs, int line_spacing);
void jfileselect_set_scrollbar_width(jfileselect *fs, int scrollbar_width);
void jfileselect_set_show_file_size(jfileselect *fs, bool show_file_size);
#endif /* _J_JFILESELECT */

View File

@ -61,6 +61,11 @@ typedef struct {
#ifdef FXCG50
char const *labels;
char const *overrides[6];
int bg_color, bg_special_color;
int text_color, text_special_color;
font_t const *font;
#endif
} jfkeys;
@ -103,15 +108,25 @@ int jfkeys_level(jfkeys *keys);
void jfkeys_set_level(jfkeys *keys, int level);
/* The following functions are available only on fx-CG 50 and are no-ops on
fx-9860 (you can't generate a good image for a tiny key). */
fx-9860G (you can't generate a good image for a tiny key). */
/* fkeys_override(): Get the override for a key */
char const *jfkeys_override(jfkeys *keys, int key);
/* fkeys_set_override(): Override the value of a single key on all levels
/* jfkeys_set_override(): Override the value of a single key on all levels
This functions sets the override on the specified key, which replaces the
label for that key on all levels. This is useful to conditionally show
functions. */
void jfkeys_set_override(jfkeys *keys, int key, char const *override);
/* jfkeys_set_color(): Change the key and text colors
* bg is the background color for MENU, ENTRY and ACTION keys (default
C_BLACK), and the border color for SPECIAL keys.
* bg_special is the background color for SPECIAL keys (default C_WHITE).
* text is the text color for MENU, ENTRY and ACTION keys (default C_WHITE).
* text_special is the text color for SPECIAL keys (default C_BLACK). */
void jfkeys_set_color(jfkeys *keys, int bg, int bg_special, int text,
int text_special);
void jfkeys_set_font(jfkeys *keys, font_t const *font);
#endif /* _J_JFKEYS */

91
include/justui/jframe.h Normal file
View File

@ -0,0 +1,91 @@
//---
// JustUI.jframe: Scrolling frame holding a widget
//---
#ifndef _J_JFRAME
#define _J_JFRAME
#include <justui/defs.h>
#include <justui/jwidget.h>
/* jframe: Scrolling frame holding a widget
This widget is used to implement scrolling widgets. It has a single child,
which is displayed fully if it's smaller than the frame, or partially (with
scrollbars) otherwise.
The child widget has horizontal and vertical alignments, which specify its
position within the frame when smaller than the frame. Its position when
larger than the frame is determined by the scrolling offsets, which can be
manipulated manually or left for the frame to control with arrow keys.
Scrollbars can be set to either render on top of the framed widget, or
occupy dedicated space. */
typedef struct {
jwidget widget;
/* Horizontal and vertical alignment for the child */
jalign halign, valign;
/* Force scrollbars even if the child is smaller than the frame */
bool scrollbars_always_visible;
/* Scrollbars render on top of the child widget */
bool floating_scrollbars;
/* Scrolling can be handled by the frame itself, with arrow keys */
bool keyboard_control;
/* Force matching the width and/or height of the child widget */
bool match_width, match_height;
/* Scrollbar width, in pixels */
uint8_t scrollbar_width;
/* If floating_scrollbars == false, spacing between scrollbars and child */
uint8_t scrollbar_spacing;
/* Visibility margin (see jframe_scroll_to_region()) */
uint8_t visibility_margin_x, visibility_margin_y;
/* Whether scrollbars are shown */
bool scrollbar_x, scrollbar_y;
/* Current scroll offsets */
int16_t scroll_x, scroll_y;
/* Maximum scroll offsets for the current size of the child widget */
int16_t max_scroll_x, max_scroll_y;
} jframe;
/* jframe_create(): Create a new frame
The frame's inner widget is always its first child. It can be specified by
jwidget_set_parent() or by creating the child with the frame as a parent
directy. More children can be added, but they will not be rendered. */
jframe *jframe_create(void *parent);
/* Trivial properties */
void jframe_set_align(jframe *f, jalign halign, jalign valign);
void jframe_set_scrollbars_always_visible(jframe *f, bool always_visible);
void jframe_set_floating_scrollbars(jframe *f, bool floating_scrollbars);
void jframe_set_keyboard_control(jframe *f, bool keyboard_control);
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.
If clamp is set to false, the frame will allow scrolling beyond current
boundaries, which is helpful as a hack when calling this function while a
layout is occuring. */
void jframe_scroll_to_region(jframe *f, jrect region, bool clamp);
#endif /* _J_JFRAME */

View File

@ -18,7 +18,7 @@
an indicator displays the status of modifier keys.
The edition rules support both the OS' native one-key-at-time input system,
and the usual computer modifer-keys-held method.
and the usual computer modifier-keys-held method.
* The normal insertion mode is used by default.
* When pressing SHIFT or ALPHA in combination with a key (without releasing

102
include/justui/jlist.h Normal file
View 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 */

View 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 */

View File

@ -89,8 +89,10 @@ typedef struct jwidget {
uint visible :1;
/* Widget is floating outside the layout (and positioned manually) */
uint floating :1;
/* Widget is clipped during rendering */
uint clipped :1;
uint :23;
uint :22;
} jwidget;
@ -358,6 +360,23 @@ bool jwidget_visible(void *w);
/* jwidget_set_visible(): Hide or show a widget */
void jwidget_set_visible(void *w, bool visible);
/* jwidget_clipped(): Whether widget is clipped
If a widget is clipped then its rendering function cannot draw pixels
outside of its bounding box. There is no performance cost to this feature
because it relies on underlying gint rendering functions already supporting
clipping.
This is disabled by default because it is convenient to have widgets draw
outside their bounding box. For instance it is easier to align a single-
line label by setting the font's bearing as its height, and then drawing
glyphs' tails outside the bouding box. It is also harder to spot layout
issues if the widgets are clipped away. */
bool jwidget_clipped(void *w);
/* jwidget_set_clipped(): Set a widget's rendering clipping preference */
void jwidget_set_clipped(void *w, bool clipped);
/* jwidget_needs_update(): Check whether the tree needs to be re-rendered
If this function returns true, you should re-render the tree. Aditionally,

View File

@ -56,12 +56,21 @@ jfileselect *jfileselect_create(void *parent)
fs->selected_file = NULL;
fs->saveas_input = input;
fs->saveas = false;
fs->input_mode = false;
fs->filter_function = jfileselect_default_filter;
fs->cursor = -1;
fs->scroll = 0;
fs->visible_lines = 0;
#ifdef FX9860G
fs->line_spacing = 1;
fs->scrollbar_width = 1;
#else
fs->line_spacing = 4;
fs->scrollbar_width = 2;
#endif
fs->font = dfont_default();
fs->show_file_size = false;
@ -127,6 +136,11 @@ void jfileselect_set_line_spacing(jfileselect *fs, int line_spacing)
count_visible_lines(fs);
}
void jfileselect_set_scrollbar_width(jfileselect *fs, int scrollbar_width)
{
fs->scrollbar_width = scrollbar_width;
}
void jfileselect_set_show_file_size(jfileselect *fs, bool show_file_size)
{
fs->show_file_size = show_file_size;
@ -166,28 +180,14 @@ static char *path_up(char const *path)
return parent;
}
static bool accept_entry(struct dirent *ent)
{
/* TODO: jfileselect: Programmable filter */
if(!strcmp(ent->d_name, "@MainMem"))
return false;
if(!strcmp(ent->d_name, "SAVE-F"))
return false;
if(!strcmp(ent->d_name, "."))
return false;
if(!strcmp(ent->d_name, ".."))
return false;
return true;
}
static int count_accepted_entries(DIR *dp)
static int count_accepted_entries(jfileselect *fs, DIR *dp)
{
int n = 0;
struct dirent *ent;
rewinddir(dp);
while((ent = readdir(dp)))
n += accept_entry(ent);
n += (fs->filter_function ? fs->filter_function(ent) : 1);
return n;
}
@ -222,7 +222,7 @@ static bool load_folder_switch(jfileselect *fs, char *path)
return false;
/* Count entries */
int n = count_accepted_entries(dp) + fs->saveas;
int n = count_accepted_entries(fs, dp) + fs->saveas;
/* Allocate memory for the fileinfo structures */
struct fileinfo *finfo = malloc(n * sizeof *finfo);
@ -234,7 +234,7 @@ static bool load_folder_switch(jfileselect *fs, char *path)
/* Read the fileinfo structures */
rewinddir(dp);
for(int i = 0; i < n && (ent = readdir(dp));) {
if(!accept_entry(ent))
if(fs->filter_function && !fs->filter_function(ent))
continue;
finfo[i].name = strdup(ent->d_name);
@ -254,7 +254,7 @@ static bool load_folder_switch(jfileselect *fs, char *path)
if(ent->d_type == DT_DIR) {
DIR *sub = opendir(full_path);
if(sub) {
finfo[i].size = count_accepted_entries(sub);
finfo[i].size = count_accepted_entries(fs, sub);
closedir(sub);
}
}
@ -320,6 +320,25 @@ char const *jfileselect_current_folder(jfileselect *fs)
return fs->path;
}
void jfileselect_set_filter(jfileselect *fs,
bool (*filter)(struct dirent const *entry))
{
fs->filter_function = filter;
}
bool jfileselect_default_filter(struct dirent const *ent)
{
if(!strcmp(ent->d_name, "@MainMem"))
return false;
if(!strcmp(ent->d_name, "SAVE-F"))
return false;
if(!strcmp(ent->d_name, "."))
return false;
if(!strcmp(ent->d_name, ".."))
return false;
return true;
}
//---
// Polymorphic widget operations
//---
@ -330,7 +349,7 @@ static void jfileselect_poly_csize(void *fs0)
jwidget *w = &fs->widget;
w->w = 128;
w->h = 6 * max(fs->font->line_height + fs->line_spacing, 0);
w->h = 3 * max(fs->font->line_height + fs->line_spacing, 0);
}
static void jfileselect_poly_layout(void *fs0)
@ -340,6 +359,23 @@ static void jfileselect_poly_layout(void *fs0)
fs->saveas_input->widget.w = jwidget_content_width(fs) - 4;
}
static void generate_info_string(char *str, bool isfolder, int size)
{
#ifdef FX9860G
if(isfolder)
sprintf(str, "%d/", size);
else if(size < 10000) /* 10 kB */
sprintf(str, "%d", size);
else
sprintf(str, "%dk", size / 1000);
#else
if(isfolder)
sprintf(str, "%d entries", size);
else
sprintf(str, "%d B", size);
#endif
}
static void jfileselect_poly_render(void *fs0, int x, int y)
{
jfileselect *fs = fs0;
@ -348,9 +384,15 @@ static void jfileselect_poly_render(void *fs0, int x, int y)
font_t const *old_font = dfont(fs->font);
int line_height = fs->font->line_height + fs->line_spacing;
int cw = jwidget_content_width(fs);
int cw = jwidget_content_width(fs) - 2 * fs->scrollbar_width;
int ch = jwidget_content_height(fs);
struct fileinfo *finfo = fs->entries;
bool scrollbar =
fs->entry_count > fs->visible_lines
&& fs->scrollbar_width > 0;
int entry_width = cw - (scrollbar ? 2 * fs->scrollbar_width : 0);
for(int i = 0; i < fs->visible_lines && i < fs->entry_count; i++) {
bool selected = (fs->cursor == fs->scroll + i);
struct fileinfo *info = &finfo[fs->scroll + i];
@ -369,16 +411,29 @@ static void jfileselect_poly_render(void *fs0, int x, int y)
continue;
}
if(selected)
drect(x, line_y, x + cw - 1, line_y + line_height - 1, C_BLACK);
if(selected) {
drect(x, line_y, x + entry_width - 1, line_y + line_height - 1,
C_BLACK);
}
dprint(x+2, text_y, fg, "%s%s", info->name, isfolder ? "/" : "");
if(fs->show_file_size && info->size >= 0) {
dprint_opt(x + cw - 3, text_y, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP,
isfolder ? "%d entries" : "%d B", info->size);
char str[32];
generate_info_string(str, isfolder, info->size);
dtext_opt(x + entry_width - 3, text_y, fg, C_NONE, DTEXT_RIGHT,
DTEXT_TOP, str);
}
}
if(scrollbar) {
int sb_y = ch * fs->scroll / fs->entry_count;
int sb_h = ch * fs->visible_lines / fs->entry_count;
drect(x + cw - fs->scrollbar_width, y + sb_y,
x + cw - 1, y + sb_y + sb_h - 1,
C_BLACK);
}
dfont(old_font);
}

View File

@ -36,6 +36,11 @@ jfkeys *jfkeys_create(char const *labels, void *parent)
jwidget_init(&f->widget, jfkeys_type_id, parent);
jfkeys_set(f, labels);
for(int i = 0; i < 6; i++) f->overrides[i] = NULL;
f->bg_color = C_BLACK;
f->bg_special_color = C_WHITE;
f->text_color = C_WHITE;
f->text_special_color = C_BLACK;
f->font = dfont_default();
return f;
}
@ -50,7 +55,11 @@ void jfkeys_set(jfkeys *f, char const *labels)
static char const *get_level(char const *labels, int level)
{
/* Navigate to level */
while(level > 0) labels = strchrnul(labels, '|');
while(level > 0) {
labels = strchrnul(labels, '|');
labels += (*labels == '|');
level--;
}
return (*labels == 0) ? NULL : labels;
}
@ -111,7 +120,7 @@ static void jfkeys_poly_render(void *f0, int base_x, int y)
#endif
#ifdef FXCG50
font_t const *old_font = dfont(dfont_default());
font_t const *old_font = dfont(f->font);
char const *level = get_level(f->labels, f->level);
if(!level) return;
@ -127,27 +136,31 @@ static void jfkeys_poly_render(void *f0, int base_x, int y)
int x = base_x + 4 + 65 * position;
int w = 63;
int color = (text[0] == '#') ? C_BLACK : C_WHITE;
int color = (text[0] == '#') ? f->text_special_color : f->text_color;
if(text[0] == '.') {
drect(x, y, x + w - 1, y + 14, C_BLACK);
drect(x, y, x + w - 1, y + 14, f->bg_color);
}
if(text[0] == '/' || text[0] == '@') {
dline(x + 1, y, x + w - 2, y, C_BLACK);
dline(x + 1, y + 14, x + w - 2, y + 14, C_BLACK);
drect(x, y + 1, x + w - 1, y + 13, C_BLACK);
if(text[0] == '@') {
dline(x + 1, y, x + w - 2, y, f->bg_color);
dline(x + 1, y + 14, x + w - 2, y + 14, f->bg_color);
drect(x, y + 1, x + w - 1, y + 13, f->bg_color);
}
if(text[0] == '/') {
dline(x + w - 1, y + 10, x + w - 5, y + 14, C_WHITE);
dline(x + w - 1, y + 11, x + w - 4, y + 14, C_WHITE);
dline(x + w - 1, y + 12, x + w - 3, y + 14, C_WHITE);
dline(x + w - 1, y + 13, x + w - 2, y + 14, C_WHITE);
dline(x + 1, y, x + w - 2, y, f->bg_color);
drect(x, y + 1, x + w - 1, y + 9, f->bg_color);
dline(x, y + 10, x + w - 2, y + 10, f->bg_color);
dline(x, y + 11, x + w - 3, y + 11, f->bg_color);
dline(x, y + 12, x + w - 4, y + 12, f->bg_color);
dline(x, y + 13, x + w - 5, y + 13, f->bg_color);
dline(x, y + 14, x + w - 6, y + 14, f->bg_color);
}
if(text[0] == '#') {
dline(x + 1, y, x + w - 2, y, C_BLACK);
dline(x + 1, y + 14, x + w - 2, y + 14, C_BLACK);
drect(x, y + 1, x + 1, y + 13, C_BLACK);
drect(x + w - 2, y + 1, x + w - 1, y + 13, C_BLACK);
dline(x + 1, y, x + w - 2, y, f->bg_color);
dline(x + 1, y + 14, x + w - 2, y + 14, f->bg_color);
drect(x, y + 1, x + 1, y + 13, f->bg_color);
drect(x + w - 2, y + 1, x + w - 1, y + 13, f->bg_color);
drect(x + 2, y + 1, x + w - 3, y + 13, f->bg_special_color);
}
dtext_opt(x + (w >> 1), y + 3, color, C_NONE, DTEXT_CENTER, DTEXT_TOP,
@ -189,6 +202,24 @@ void jfkeys_set_override(GUNUSED jfkeys *keys, GUNUSED int key,
#endif
}
void jfkeys_set_color(GUNUSED jfkeys *keys, GUNUSED int bg,
GUNUSED int bg_special, GUNUSED int text, GUNUSED int text_special)
{
#ifdef FXCG50
keys->bg_color = bg;
keys->bg_special_color = bg_special;
keys->text_color = text;
keys->text_special_color = text_special;
#endif
}
void jfkeys_set_font(GUNUSED jfkeys *keys, GUNUSED font_t const *font)
{
#ifdef FXCG50
keys->font = font;
#endif
}
/* jfkeys type definition */
static jwidget_poly type_jfkeys = {
.name = "jfkeys",

392
src/jframe.c Normal file
View File

@ -0,0 +1,392 @@
#include <justui/jwidget.h>
#include <justui/jwidget-api.h>
#include <justui/jframe.h>
#include "util.h"
#include <gint/display.h>
#include <stdlib.h>
/* Type identifier for jframe */
static int jframe_type_id = -1;
jframe *jframe_create(void *parent)
{
if(jframe_type_id < 0)
return NULL;
jframe *f = malloc(sizeof *f);
if(!f)
return NULL;
jwidget_init(&f->widget, jframe_type_id, parent);
jwidget_set_clipped(f, true);
f->halign = J_ALIGN_CENTER;
f->valign = J_ALIGN_MIDDLE;
f->scrollbars_always_visible = false;
f->floating_scrollbars = false;
f->keyboard_control = false;
f->match_width = false;
f->match_height = false;
#ifdef FX9860G
f->scrollbar_width = 1;
f->scrollbar_spacing = 1;
f->visibility_margin_x = 4;
f->visibility_margin_y = 4;
#else
f->scrollbar_width = 2;
f->scrollbar_spacing = 2;
f->visibility_margin_x = 8;
f->visibility_margin_y = 8;
#endif
f->scroll_x = 0;
f->scroll_y = 0;
f->max_scroll_x = 0;
f->max_scroll_y = 0;
return f;
}
static jwidget *frame_child(jframe *f)
{
if(f->widget.child_count == 0)
return NULL;
return f->widget.children[0];
}
static int frame_scrollbar_space_size(jframe *f)
{
if(f->floating_scrollbars)
return 0;
return f->scrollbar_width + f->scrollbar_spacing;
}
static void shake(jframe *f)
{
f->scroll_x = clamp(f->scroll_x, 0, f->max_scroll_x);
f->scroll_y = clamp(f->scroll_y, 0, f->max_scroll_y);
}
/* Start position of a block of size B, aligned as specified, inside a space of
size S; returned as a value in [0..s). */
static int aligned_start_within(int B, int S, jalign align)
{
if(align == J_ALIGN_LEFT || align == J_ALIGN_TOP)
return 0;
else if(align == J_ALIGN_RIGHT || align == J_ALIGN_BOTTOM)
return S - B;
else
return (S - B) / 2;
}
//---
// Getters and setters
//---
void jframe_set_align(jframe *f, jalign halign, jalign valign)
{
if(f->halign == halign && f->valign == valign)
return;
f->halign = halign;
f->valign = valign;
f->widget.update = 1;
}
void jframe_set_scrollbars_always_visible(jframe *f, bool always_visible)
{
if(f->scrollbars_always_visible == always_visible)
return;
f->scrollbars_always_visible = always_visible;
f->widget.dirty = 1;
}
void jframe_set_floating_scrollbars(jframe *f, bool floating_scrollbars)
{
if(f->floating_scrollbars == floating_scrollbars)
return;
f->floating_scrollbars = floating_scrollbars;
f->widget.dirty = 1;
}
void jframe_set_keyboard_control(jframe *f, bool 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, bool do_clamp)
{
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 */
if(do_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;
}
//---
// Polymorphic widget operations
//---
static void jframe_poly_csize(void *f0)
{
jframe *f = f0;
jwidget *w = &f->widget;
jwidget *child = frame_child(f);
w->w = w->h = 16;
if(child) {
jwidget_msize(child);
if(f->match_width)
w->w = child->w;
if(f->match_height)
w->h = child->h;
}
int frame_sss = frame_scrollbar_space_size(f);
if(!f->floating_scrollbars) {
w->w += frame_sss;
w->h += frame_sss;
}
jwidget_set_minimum_size(f, frame_sss + 4, frame_sss + 4);
}
static void jframe_poly_layout(void *f0)
{
jframe *f = f0;
jwidget *child = frame_child(f);
if(!child) {
f->scrollbar_x = f->scrollbar_y = f->scrollbars_always_visible;
f->scroll_x = f->scroll_y = 0;
f->max_scroll_x = f->max_scroll_y = 0;
return;
}
int child_w = jwidget_full_width(child);
int child_h = jwidget_full_height(child);
int frame_w = jwidget_content_width(f);
int frame_h = jwidget_content_height(f);
int frame_sss = frame_scrollbar_space_size(f);
int sss_x = 0;
int sss_y = 0;
/* We enable scrollbars if:
(1) They were forced in; or
(2) The child widget wouldn't fit without them.
Scrollbars are linked; adding a scrollbar for one direction can reduce
the space available in the other direction, thus causing the other
scrollbar to appear. Hence, we need to iterate. */
f->scrollbar_y = f->scrollbars_always_visible || child_h + sss_y > frame_h;
if(f->scrollbar_y)
sss_x = frame_sss;
f->scrollbar_x = f->scrollbars_always_visible || child_w + sss_x > frame_w;
if(f->scrollbar_x)
sss_y = frame_sss;
f->scrollbar_y = f->scrollbars_always_visible || child_h + sss_y > frame_h;
if(f->scrollbar_y)
sss_x = frame_sss;
/* At this stage we have a fixpoint, because:
- x is up-to-date. x can only be outdated if the 2nd y check just
enabled scrollbar_y. But it can only do so if the x check enabled
scrollbar_x, in which case scrollbar_x is already a stable true.
- y is up-to-date since it was re-checked after x's last update. */
f->max_scroll_x = max(0, child_w - (frame_w - sss_x));
f->max_scroll_y = max(0, child_h - (frame_h - sss_y));
shake(f);
/* We can now set the inner widget's dimensions. The frame acts as a
container, and thus sets the child's size, applying strech etc. One
unique trait of the frame is that the child *always* gets its desired
size since we can scroll it. */
if(child->stretch_x > 0)
child->w = max(child->w, min(frame_w, child->max_w));
if(child->stretch_y > 0)
child->h = max(child->h, min(frame_h, child->max_h));
}
static void jframe_poly_render(void *f0, int x, int y)
{
jframe *f = f0;
jwidget *child = frame_child(f);
int child_w = jwidget_full_width(child);
int child_h = jwidget_full_height(child);
int frame_w = jwidget_content_width(f);
int frame_h = jwidget_content_height(f);
int frame_sss = frame_scrollbar_space_size(f);
int sss_x = f->scrollbar_y ? frame_sss : 0;
int sss_y = f->scrollbar_x ? frame_sss : 0;
/* In each dimension:
- If there is scrolling, we place according to the scroll offset;
- Otherwise, we place according to alignment settings. */
int render_x;
if(f->scrollbar_x)
render_x = x - f->scroll_x;
else
render_x = x + aligned_start_within(child_w, frame_w-sss_x, f->halign);
int render_y;
if(f->scrollbar_y)
render_y = y - f->scroll_y;
else
render_y = y + aligned_start_within(child_h, frame_h-sss_y, f->valign);
/* Render the child with dedicated clipping. */
if(child) {
struct dwindow win = {x, y, x + frame_w - sss_x, y + frame_h - sss_y};
win = intersect_dwindow(win, dwindow);
struct dwindow old_window = dwindow_set(win);
jwidget_render(child, render_x, render_y);
dwindow_set(old_window);
}
/* Render the scrollbars. */
if(f->scrollbar_x) {
int sb_x = x;
int sb_y = y + frame_h - f->scrollbar_width;
int sb_left = f->scroll_x * frame_w / child_w;
int sb_width = frame_w * frame_w / child_w;
drect(sb_x + sb_left, sb_y, sb_x + sb_left + sb_width - 1,
sb_y + f->scrollbar_width - 1, C_BLACK);
}
if(f->scrollbar_y) {
int sb_x = x + frame_w - f->scrollbar_width;
int sb_y = y;
int sb_top = f->scroll_y * frame_h / child_h;
int sb_height = frame_h * frame_h / child_h;
drect(sb_x, sb_y + sb_top, sb_x + f->scrollbar_width - 1,
sb_y + sb_top + sb_height - 1, C_BLACK);
}
}
static bool jframe_poly_event(void *f0, jevent e)
{
jframe *f = f0;
if(!f->keyboard_control || e.type != JWIDGET_KEY)
return false;
key_event_t ev = e.key;
if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD)
return false;
int key = ev.key;
if(key == KEY_LEFT && f->scrollbar_x && f->scroll_x > 0) {
f->scroll_x--;
f->widget.update = 1;
return true;
}
if(key == KEY_RIGHT && f->scrollbar_x && f->scroll_x < f->max_scroll_x-1) {
f->scroll_x++;
f->widget.update = 1;
return true;
}
if(key == KEY_UP && f->scrollbar_y && f->scroll_y > 0) {
f->scroll_y--;
f->widget.update = 1;
return true;
}
if(key == KEY_DOWN && f->scrollbar_y && f->scroll_y < f->max_scroll_y-1) {
f->scroll_y++;
f->widget.update = 1;
return true;
}
return false;
}
/* jframe type definition */
static jwidget_poly type_jframe = {
.name = "jframe",
.csize = jframe_poly_csize,
.layout = jframe_poly_layout,
.render = jframe_poly_render,
.event = jframe_poly_event,
};
__attribute__((constructor(1001)))
static void j_register_jframe(void)
{
jframe_type_id = j_register_widget(&type_jframe, "jwidget");
}

View File

@ -85,9 +85,9 @@ void jinput_set_prompt(jinput *i, char const *prompt)
static void insert_str(jinput *i, char const *str, size_t n)
{
if(i->size + n > i->max) return;
if(i->size + n > i->max || i->cursor < 0) return;
/* Insert at cursor_pos, shift everything else right n places */
/* Insert at i->cursor, shift everything else right n places */
for(int k = i->size - 1; k >= i->cursor; k--)
i->text[k + n] = i->text[k];
@ -131,7 +131,7 @@ static void insert_code_point(jinput *i, uint32_t p)
static int previous(char const *str, int position)
{
if(position == 0)
if(position <= 0)
return position;
while((str[--position] & 0xc0) == 0x80) {}
@ -148,6 +148,7 @@ static int next(char const *str, int position)
static void delete(jinput *i)
{
if(i->cursor < 0) return;
int prev = previous(i->text, i->cursor);
int diff = i->cursor - prev;
if(!diff) return;

274
src/jlist.c Normal file
View File

@ -0,0 +1,274 @@
#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) {
l->item_count = 0;
l->cursor = -1;
l->widget.dirty = 1;
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();
}

View File

@ -128,13 +128,13 @@ void jscene_set_focused_widget(jscene *s, void *w0)
}
/* Focus out old focused widget */
if(s->focus) jscene_queue_event(s,
if(s->focus) jwidget_event(s->focus,
(jevent){ .type = JWIDGET_FOCUS_OUT, .source = s->focus });
s->focus = w;
/* Focus in newly-selected widget */
if(w) jscene_queue_event(s,
if(w) jwidget_event(w,
(jevent){ .type = JWIDGET_FOCUS_IN, .source = w });
}
@ -216,7 +216,7 @@ jevent jscene_run(jscene *s)
if(e.type != JSCENE_NONE && !jscene_process_event(s, e)) break;
/* Queued keyboard events */
key_event_t k = keydev_read(d);
key_event_t k = keydev_read(d, false, NULL);
if(k.type == KEYEV_DOWN && k.key == KEY_MENU && !k.shift && !k.alpha) {
if(s->mainmenu) {
@ -231,12 +231,18 @@ jevent jscene_run(jscene *s)
continue;
}
#endif
getkey_feature_t feat = getkey_feature_function();
if((k.type == KEYEV_DOWN || k.type == KEYEV_HOLD) && feat && feat(k))
continue;
if(k.type != KEYEV_NONE && !jscene_process_key_event(s, k)) {
e.type = JWIDGET_KEY;
e.key = k;
break;
}
// TODO: Should only sleep when out of events!
sleep();
}

85
src/jscrolledlist.c Normal file
View File

@ -0,0 +1,85 @@
#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);
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;
}
static void shake_scroll(jscrolledlist *l, bool clamp)
{
int cursor = jlist_selected_item(l->list);
if(cursor >= 0) {
jrect r = jlist_selected_region(l->list);
jframe_scroll_to_region(l->frame, r, clamp);
}
}
//---
// Polymorphic widget operations
//---
static void jscrolledlist_poly_layout(void *l0)
{
jscrolledlist *l = l0;
l->frame->widget.x = 0;
l->frame->widget.y = 0;
l->frame->widget.w = jwidget_content_width(l);
l->frame->widget.h = jwidget_content_height(l);
shake_scroll(l, false);
}
static bool jscrolledlist_poly_event(void *l0, jevent e)
{
jscrolledlist *l = l0;
if(e.type == JLIST_SELECTION_MOVED && e.source == l->list)
shake_scroll(l, true);
if(e.type == JLIST_MODEL_UPDATED && e.source == l->list)
shake_scroll(l, true);
/* Allow the evnts to bubble up */
return false;
}
/* jscrolledlist type definition */
static jwidget_poly type_jscrolledlist = {
.name = "jscrolledlist",
.layout = jscrolledlist_poly_layout,
.event = jscrolledlist_poly_event,
};
__attribute__((constructor(1002)))
static void j_register_jscrolledlist(void)
{
jscrolledlist_type_id = j_register_widget(&type_jscrolledlist, "jwidget");
}

View File

@ -109,6 +109,7 @@ void jwidget_init(jwidget *w, int type, void *parent)
w->dirty = 1;
w->visible = 1;
w->floating = 0;
w->clipped = 0;
w->type = type;
w->geometry = NULL;
@ -597,6 +598,21 @@ void jwidget_set_floating(void *w0, bool floating)
if(w->parent) w->parent->dirty = 1;
}
bool jwidget_clipped(void *w0)
{
J_CAST(w)
return w->clipped;
}
void jwidget_set_clipped(void *w0, bool clipped)
{
J_CAST(w)
if(w->clipped == clipped) return;
w->clipped = (clipped != 0);
w->update = 1;
}
//---
// Rendering
//---
@ -643,7 +659,22 @@ void jwidget_render(void *w0, int x, int y)
y += g->margin.top + b.top + g->padding.top;
jwidget_poly const *poly = widget_types[w->type];
if(poly->render) poly->render(w, x, y);
if(poly->render) {
if(w->clipped) {
struct dwindow win = { x, y, x+cw, y+ch };
win = intersect_dwindow(win, dwindow);
/* Skip rendering out-of-view widgets */
if(win.right > win.left && win.bottom > win.top) {
struct dwindow old_window = dwindow_set(win);
poly->render(w, x, y);
dwindow_set(old_window);
}
}
else {
poly->render(w, x, y);
}
}
w->update = 0;
}

View File

@ -6,6 +6,7 @@
#define _J_UTIL
#include <justui/defs.h>
#include <gint/display.h>
/* Clamp a value between two ends. */
__attribute__((always_inline))
@ -20,4 +21,16 @@ static inline int clamp(int value, int min, int max)
/* Code point for a character input */
uint32_t keymap_translate(int key, bool shift, bool alpha);
/* Intersect two dwindow settings. */
static inline struct dwindow intersect_dwindow(
struct dwindow d1, struct dwindow d2)
{
struct dwindow win;
win.left = max(d1.left, d2.left);
win.top = max(d1.top, d2.top);
win.right = max(min(d1.right, d2.right), win.left);
win.bottom = max(min(d1.bottom, d2.bottom), win.top);
return win;
}
#endif /* _J_UTIL */