Compare commits
16 Commits
577e9ae20c
...
d9c18117c5
Author | SHA1 | Date |
---|---|---|
Lephenixnoir | d9c18117c5 | |
Lephenixnoir | a84b0adb3a | |
Lephenixnoir | f5d6fb2e87 | |
Lephenixnoir | 40ffe54250 | |
Lephenixnoir | beeb0c0724 | |
Lephenixnoir | 51fbf2f582 | |
Lephenixnoir | 950c5b7152 | |
Lephenixnoir | 67219834be | |
Lephenixnoir | 2e15bb8c96 | |
Lephenixnoir | 5b591a6fb4 | |
Lephenixnoir | 7c0e8257f7 | |
Lephenixnoir | cfeba2695c | |
Lephenixnoir | fd9191cc43 | |
Lephenixnoir | a588c24f59 | |
Lephenixnoir | 34ee43d67f | |
Lephenixnoir | b0195af198 |
|
@ -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}}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -43,6 +43,8 @@ typedef struct {
|
|||
union {
|
||||
/* JWIDGET_KEY events */
|
||||
key_event_t key;
|
||||
/* Custom generic value */
|
||||
int data;
|
||||
};
|
||||
|
||||
} jevent;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
63
src/jfkeys.c
63
src/jfkeys.c
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
12
src/jscene.c
12
src/jscene.c
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
13
src/util.h
13
src/util.h
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue