mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-29 13:03:40 +01:00
Compare commits
3 commits
7a5101360a
...
4728c6ecbe
Author | SHA1 | Date | |
---|---|---|---|
|
4728c6ecbe | ||
|
33e9962209 | ||
|
57a460894f |
10 changed files with 267 additions and 67 deletions
|
@ -46,8 +46,9 @@ New keyboard focus system.
|
||||||
b. There is a widget-scene call to give a widget active focus. If the widget
|
b. There is a widget-scene call to give a widget active focus. If the widget
|
||||||
has policy FOCUS_ACCEPT or FOCUS_SCOPE, this call walks up the scope
|
has policy FOCUS_ACCEPT or FOCUS_SCOPE, this call walks up the scope
|
||||||
chain and assigns new targets until it reaches the scene. If the widget
|
chain and assigns new targets until it reaches the scene. If the widget
|
||||||
has policy FOCUS_NONE, this call just removes the surrounding scope's
|
has is NULL, this call just removes the surrounding scope's target.
|
||||||
target.
|
TODO: If the widget is FOCUS_REJECT, clear scopes from that widget up
|
||||||
|
until reaching the scene.
|
||||||
X. There is a widget-context call to relinquish one's own focus within the
|
X. There is a widget-context call to relinquish one's own focus within the
|
||||||
parent scope. This is intended to differ from 2.b in that scopes might
|
parent scope. This is intended to differ from 2.b in that scopes might
|
||||||
implement special logic for moving focus to a nearby widget.
|
implement special logic for moving focus to a nearby widget.
|
||||||
|
@ -73,19 +74,20 @@ New keyboard focus system.
|
||||||
4. Behavior of built-in widgets
|
4. Behavior of built-in widgets
|
||||||
a. `jfileselect`, `jinput`, `jlist` have policy FOCUS_ACCEPT
|
a. `jfileselect`, `jinput`, `jlist` have policy FOCUS_ACCEPT
|
||||||
b. `jscene` has policy FOCUS_SCOPE
|
b. `jscene` has policy FOCUS_SCOPE
|
||||||
TODO: What about jframe?
|
|
||||||
c. `jfkeys` has policy `FOCUS_REJECT` and must be given events manually
|
c. `jfkeys` has policy `FOCUS_REJECT` and must be given events manually
|
||||||
d. Widgets with a stack layout will try and give focus (within their
|
d. Widgets with a stack layout will try and give focus (within their
|
||||||
surrounding scope) to the current element. If the element is complex, it
|
surrounding scope) to the current element. If the element is complex, it
|
||||||
should be a focus scope. Similarly, widgets with invisible children will
|
should be a focus scope. Similarly, widgets with invisible children will
|
||||||
move focus around to their direct children as needed to make sure focus
|
move focus around to their direct children as needed to make sure focus
|
||||||
remains on a visible child.
|
remains on a visible child.
|
||||||
|
TODO: Implement stack layout logic
|
||||||
|
|
||||||
5. Implementation
|
5. Implementation
|
||||||
a. Events are `FOCUS_CHANGED` when own FOCUSED and ACTIVE_FOCUSED flags have
|
a. Events are `FOCUS_CHANGED` when own FOCUSED and ACTIVE_FOCUSED flags have
|
||||||
changed, and `FOCUS_TARGET_CHANGED` for scopes when the target changes.
|
changed, and `FOCUS_TARGET_CHANGED` for scopes when the target changes.
|
||||||
|
|
||||||
NOTES:
|
NOTES:
|
||||||
|
- Update focus flags when removing from parent
|
||||||
- Focus events don't propagate but are replicated, which adds some complexity
|
- Focus events don't propagate but are replicated, which adds some complexity
|
||||||
- Focus scopes could have background focus for jfkeys (left for later). Hard
|
- Focus scopes could have background focus for jfkeys (left for later). Hard
|
||||||
part is this isn't specified by jfkeys.
|
part is this isn't specified by jfkeys.
|
||||||
|
|
|
@ -67,7 +67,7 @@ typedef struct {
|
||||||
jalign block_valign;
|
jalign block_valign;
|
||||||
/* Text alignment */
|
/* Text alignment */
|
||||||
jalign text_align;
|
jalign text_align;
|
||||||
/* Pixels of spacing between each line, in addition to font->height */
|
/* Pixels of spacing between lines, in addition to font->line_distance */
|
||||||
int8_t line_spacing;
|
int8_t line_spacing;
|
||||||
|
|
||||||
/* Text to display */
|
/* Text to display */
|
||||||
|
@ -76,12 +76,16 @@ typedef struct {
|
||||||
of line number n */
|
of line number n */
|
||||||
DECLARE_VEC(uint16_t, breaks);
|
DECLARE_VEC(uint16_t, breaks);
|
||||||
|
|
||||||
|
/* Block width (maximum length of a rendered line) */
|
||||||
|
uint16_t block_width;
|
||||||
/* Text wrapping mode */
|
/* Text wrapping mode */
|
||||||
enum jwrapmode wrap_mode;
|
enum jwrapmode wrap_mode;
|
||||||
/* Whether the text has been allocated by the label or supplied by user */
|
/* Whether the text has been allocated by the label or supplied by user */
|
||||||
int8_t owns_text;
|
bool owns_text :1;
|
||||||
/* Block width (maximum length of a rendered line) */
|
/* Whether to preserve spaces around line-wrapping newlines */
|
||||||
uint16_t block_width;
|
bool wrapped_newline_spacing :1;
|
||||||
|
|
||||||
|
uint :5;
|
||||||
|
|
||||||
/* Color and font of text; if NULL, gint's default font is used */
|
/* Color and font of text; if NULL, gint's default font is used */
|
||||||
int color;
|
int color;
|
||||||
|
@ -130,5 +134,6 @@ void jlabel_set_line_spacing(jlabel *l, int line_spacing);
|
||||||
void jlabel_set_wrap_mode(jlabel *l, jwrapmode mode);
|
void jlabel_set_wrap_mode(jlabel *l, jwrapmode mode);
|
||||||
void jlabel_set_text_color(jlabel *l, int color);
|
void jlabel_set_text_color(jlabel *l, int color);
|
||||||
void jlabel_set_font(jlabel *l, font_t const *font);
|
void jlabel_set_font(jlabel *l, font_t const *font);
|
||||||
|
void jlabel_set_wrapped_newline_spacing(jlabel *l, bool preserve);
|
||||||
|
|
||||||
#endif /* _J_JLABEL */
|
#endif /* _J_JLABEL */
|
||||||
|
|
|
@ -21,8 +21,6 @@ typedef struct {
|
||||||
|
|
||||||
/* Location on screen */
|
/* Location on screen */
|
||||||
int16_t x, y;
|
int16_t x, y;
|
||||||
/* Widget with focus */
|
|
||||||
jwidget *focus;
|
|
||||||
|
|
||||||
/* Circular event queue */
|
/* Circular event queue */
|
||||||
jevent queue[JSCENE_QUEUE_SIZE];
|
jevent queue[JSCENE_QUEUE_SIZE];
|
||||||
|
|
|
@ -67,6 +67,9 @@ typedef struct jwidget {
|
||||||
jlayout_grid layout_grid;
|
jlayout_grid layout_grid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Focused subwidget for focus scopes */
|
||||||
|
struct jwidget *focus_scope_target;
|
||||||
|
|
||||||
/* Widget type, used to find polymorphic operations */
|
/* Widget type, used to find polymorphic operations */
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
/* Number of children */
|
/* Number of children */
|
||||||
|
@ -91,8 +94,13 @@ typedef struct jwidget {
|
||||||
uint floating :1;
|
uint floating :1;
|
||||||
/* Widget is clipped during rendering */
|
/* Widget is clipped during rendering */
|
||||||
uint clipped :1;
|
uint clipped :1;
|
||||||
|
/* Focus policy */
|
||||||
|
uint focus_policy :2;
|
||||||
|
/* Focus flags */
|
||||||
|
uint focused :1;
|
||||||
|
uint active_focused :1;
|
||||||
|
|
||||||
uint :22;
|
uint :18;
|
||||||
|
|
||||||
} jwidget;
|
} jwidget;
|
||||||
|
|
||||||
|
@ -106,6 +114,17 @@ typedef enum {
|
||||||
|
|
||||||
} jwidget_border_style;
|
} jwidget_border_style;
|
||||||
|
|
||||||
|
/* jwidget_focus_policy: Options for receiving and handling keyboard focus */
|
||||||
|
typedef enum {
|
||||||
|
/* The widget does not accept focus or interact with keyboard (default) */
|
||||||
|
J_FOCUS_POLICY_REJECT,
|
||||||
|
/* The widget accepts keyboard input and hides it from descendants */
|
||||||
|
J_FOCUS_POLICY_ACCEPT,
|
||||||
|
/* The widget accepts keyboard focus and forwards it to a descendant */
|
||||||
|
J_FOCUS_POLICY_SCOPE,
|
||||||
|
|
||||||
|
} jwidget_focus_policy_t;
|
||||||
|
|
||||||
/* jwidget_geometry: Built-in positioning and border geometry
|
/* jwidget_geometry: Built-in positioning and border geometry
|
||||||
|
|
||||||
Every widget has a "geometry", which consists of a border and two layers of
|
Every widget has a "geometry", which consists of a border and two layers of
|
||||||
|
@ -143,14 +162,13 @@ typedef struct jwidget_geometry {
|
||||||
|
|
||||||
} jwidget_geometry;
|
} jwidget_geometry;
|
||||||
|
|
||||||
/* Downwards key event: widget is notified of a key press that ocurred while it
|
/* Key press event; the widget has active focus and a key was pressed.
|
||||||
had active focus.
|
|
||||||
-> .data.key: Key event */
|
-> .data.key: Key event */
|
||||||
extern uint16_t JWIDGET_KEY;
|
extern uint16_t JWIDGET_KEY;
|
||||||
/* Downwards focus-in event: the widget has just received focus */
|
/* The widget's focus state (either .focused or .active_focused) changed. */
|
||||||
extern uint16_t JWIDGET_FOCUS_IN;
|
extern uint16_t JWIDGET_FOCUS_CHANGED;
|
||||||
/* Downwards focus-out event: the widget has just lost focus */
|
/* The widget is a scope and its target changed. */
|
||||||
extern uint16_t JWIDGET_FOCUS_OUT;
|
extern uint16_t JWIDGET_FOCUS_TARGET_CHANGED;
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Creation and destruction
|
// Creation and destruction
|
||||||
|
@ -395,6 +413,53 @@ bool jwidget_needs_update(void *w);
|
||||||
widgets if there has been changes. */
|
widgets if there has been changes. */
|
||||||
void jwidget_render(void *w, int x, int y);
|
void jwidget_render(void *w, int x, int y);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Keyboard focus
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Change the widget's focus policy. This is usually done in the constructor
|
||||||
|
but can be done anytime. If the policy is changed to JWIDGET_FOCUS_REJECT
|
||||||
|
while the widget has focus, the focus will be lost. */
|
||||||
|
void jwidget_set_focus_policy(void *w, jwidget_focus_policy_t fp);
|
||||||
|
|
||||||
|
/* Check whether a widget is currently focused within its surrounding scope. */
|
||||||
|
GINLINE static bool jwidget_has_focus(void *w)
|
||||||
|
{
|
||||||
|
return ((jwidget *)w)->focused;
|
||||||
|
}
|
||||||
|
/* Check whether a widget is current in the key event propagation chain. */
|
||||||
|
GINLINE static bool jwidget_has_active_focus(void *w)
|
||||||
|
{
|
||||||
|
return ((jwidget *)w)->active_focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change the target of a focus scope widget. */
|
||||||
|
void jwidget_scope_set_target(void *fs, void *target);
|
||||||
|
|
||||||
|
/* Get the target of a focus scope, NULL if none or not a scope. */
|
||||||
|
GINLINE static jwidget *jwidget_scope_get_target(void *fs)
|
||||||
|
{
|
||||||
|
return ((jwidget *)fs)->focus_scope_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear the target of a focus scope widget. */
|
||||||
|
GINLINE static void jwidget_scope_clear_focus(void *fs)
|
||||||
|
{
|
||||||
|
return jwidget_scope_set_target(fs, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Context function that returns the immediately surrounding scope that owns
|
||||||
|
this widget, NULL if there's none. Since jscene is a scope, in general there
|
||||||
|
should always be one. */
|
||||||
|
jwidget *jwidgetctx_enclosing_focus_scope(void *w);
|
||||||
|
|
||||||
|
/* Context function that gives w focus within its enclosing focus scope. */
|
||||||
|
void jwidgetctx_grab_focus(void *w);
|
||||||
|
|
||||||
|
/* Context function that drops the focus from w (if it has it) within its
|
||||||
|
enclosing focus scope. */
|
||||||
|
void jwidgetctx_drop_focus(void *w);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Misc
|
// Misc
|
||||||
//---
|
//---
|
||||||
|
|
|
@ -52,6 +52,7 @@ jfileselect *jfileselect_create(void *parent)
|
||||||
if(!fs) return NULL;
|
if(!fs) return NULL;
|
||||||
|
|
||||||
jwidget_init(&fs->widget, jfileselect_type_id, parent);
|
jwidget_init(&fs->widget, jfileselect_type_id, parent);
|
||||||
|
jwidget_set_focus_policy(fs, J_FOCUS_POLICY_SCOPE);
|
||||||
|
|
||||||
jinput *input = jinput_create("Filename: ", 32, fs);
|
jinput *input = jinput_create("Filename: ", 32, fs);
|
||||||
if(!input) {
|
if(!input) {
|
||||||
|
@ -116,14 +117,14 @@ static void start_input(jfileselect *fs)
|
||||||
{
|
{
|
||||||
fs->input_mode = true;
|
fs->input_mode = true;
|
||||||
jinput_clear(fs->saveas_input);
|
jinput_clear(fs->saveas_input);
|
||||||
jwidget_event(fs->saveas_input, (jevent){ .type = JWIDGET_FOCUS_IN });
|
jwidget_scope_set_target(fs, fs->saveas_input);
|
||||||
fs->widget.update = true;
|
fs->widget.update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stop_input(jfileselect *fs)
|
static void stop_input(jfileselect *fs)
|
||||||
{
|
{
|
||||||
fs->input_mode = false;
|
fs->input_mode = false;
|
||||||
jwidget_event(fs->saveas_input, (jevent){ .type = JWIDGET_FOCUS_OUT });
|
jwidget_scope_set_target(fs, NULL);
|
||||||
fs->widget.update = true;
|
fs->widget.update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/jinput.c
11
src/jinput.c
|
@ -45,6 +45,7 @@ jinput *jinput_create(char const *prompt, size_t length, void *parent)
|
||||||
if(!i) return NULL;
|
if(!i) return NULL;
|
||||||
|
|
||||||
jwidget_init(&i->widget, jinput_type_id, parent);
|
jwidget_init(&i->widget, jinput_type_id, parent);
|
||||||
|
jwidget_set_focus_policy(i, J_FOCUS_POLICY_ACCEPT);
|
||||||
|
|
||||||
i->color = C_BLACK;
|
i->color = C_BLACK;
|
||||||
jinput_set_font(i, NULL);
|
jinput_set_font(i, NULL);
|
||||||
|
@ -242,14 +243,8 @@ static bool jinput_poly_event(void *i0, jevent e)
|
||||||
{
|
{
|
||||||
jinput *i = i0;
|
jinput *i = i0;
|
||||||
|
|
||||||
if(e.type == JWIDGET_FOCUS_IN) {
|
if(e.type == JWIDGET_FOCUS_CHANGED) {
|
||||||
i->cursor = i->size - 1;
|
i->cursor = jwidget_has_focus(i) ? (i->size - 1) : -1;
|
||||||
i->mode = 0;
|
|
||||||
i->widget.update = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(e.type == JWIDGET_FOCUS_OUT) {
|
|
||||||
i->cursor = -1;
|
|
||||||
i->mode = 0;
|
i->mode = 0;
|
||||||
i->widget.update = 1;
|
i->widget.update = 1;
|
||||||
}
|
}
|
||||||
|
|
20
src/jlabel.c
20
src/jlabel.c
|
@ -21,16 +21,17 @@ jlabel *jlabel_create(char const *text, void *parent)
|
||||||
jwidget_init(&l->widget, jlabel_type_id, parent);
|
jwidget_init(&l->widget, jlabel_type_id, parent);
|
||||||
|
|
||||||
l->block_halign = J_ALIGN_LEFT;
|
l->block_halign = J_ALIGN_LEFT;
|
||||||
l->block_valign = J_ALIGN_MIDDLE;
|
l->block_valign = J_ALIGN_TOP;
|
||||||
l->text_align = J_ALIGN_LEFT;
|
l->text_align = J_ALIGN_LEFT;
|
||||||
|
|
||||||
l->line_spacing = 1;
|
l->line_spacing = 0;
|
||||||
l->color = C_BLACK;
|
l->color = C_BLACK;
|
||||||
l->font = NULL;
|
l->font = NULL;
|
||||||
|
|
||||||
l->wrap_mode = J_WRAP_NONE;
|
l->wrap_mode = J_WRAP_NONE;
|
||||||
l->text = text;
|
l->text = text;
|
||||||
l->owns_text = false;
|
l->owns_text = false;
|
||||||
|
l->wrapped_newline_spacing = false;
|
||||||
|
|
||||||
vec_init(&l->breaks_vec, sizeof *l->breaks);
|
vec_init(&l->breaks_vec, sizeof *l->breaks);
|
||||||
|
|
||||||
|
@ -188,6 +189,12 @@ void jlabel_set_font(jlabel *l, font_t const *font)
|
||||||
l->widget.dirty = 1;
|
l->widget.dirty = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void jlabel_set_wrapped_newline_spacing(jlabel *l, bool preserve)
|
||||||
|
{
|
||||||
|
l->wrapped_newline_spacing = preserve;
|
||||||
|
l->widget.dirty = 1;
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Polymorphic widget operations
|
// Polymorphic widget operations
|
||||||
//---
|
//---
|
||||||
|
@ -236,7 +243,7 @@ static void jlabel_poly_layout(void *l0)
|
||||||
/* Start of line */
|
/* Start of line */
|
||||||
add_break(l, str - l->text);
|
add_break(l, str - l->text);
|
||||||
|
|
||||||
/* A "\n" forces a newline in all wrap omdes */
|
/* A "\n" forces a newline in all wrap modes */
|
||||||
char const *end_of_line = strchrnul(str, '\n');
|
char const *end_of_line = strchrnul(str, '\n');
|
||||||
|
|
||||||
/* Also consider word or letters boundaries */
|
/* Also consider word or letters boundaries */
|
||||||
|
@ -257,14 +264,17 @@ static void jlabel_poly_layout(void *l0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool natural_break = (*end_of_line == '\n');
|
||||||
char const *next_start = end_of_line + (*end_of_line == '\n');
|
char const *next_start = end_of_line + (*end_of_line == '\n');
|
||||||
|
|
||||||
|
if(!natural_break && !l->wrapped_newline_spacing) {
|
||||||
/* Skip trailing spaces on this line */
|
/* Skip trailing spaces on this line */
|
||||||
while(end_of_line > str && end_of_line[-1] == ' ')
|
while(end_of_line > str && end_of_line[-1] == ' ')
|
||||||
end_of_line--;
|
end_of_line--;
|
||||||
/* Skip leading spaces on the next line */
|
/* Skip leading spaces on the next line */
|
||||||
while(next_start[0] == ' ')
|
while(next_start[0] == ' ')
|
||||||
next_start++;
|
next_start++;
|
||||||
|
}
|
||||||
|
|
||||||
add_break(l, end_of_line - l->text);
|
add_break(l, end_of_line - l->text);
|
||||||
|
|
||||||
|
@ -295,7 +305,7 @@ static void jlabel_poly_render(void *l0, int x, int y)
|
||||||
/* Position the block vertically */
|
/* Position the block vertically */
|
||||||
|
|
||||||
int lines = l->breaks_vec.size / 2;
|
int lines = l->breaks_vec.size / 2;
|
||||||
int block_height = lines * (f->line_height + l->line_spacing) -
|
int block_height = lines * (f->line_distance + l->line_spacing) -
|
||||||
l->line_spacing;
|
l->line_spacing;
|
||||||
|
|
||||||
if(l->block_valign == J_ALIGN_MIDDLE)
|
if(l->block_valign == J_ALIGN_MIDDLE)
|
||||||
|
@ -331,7 +341,7 @@ static void jlabel_poly_render(void *l0, int x, int y)
|
||||||
dtext_opt(x + dx, y, l->color, C_NONE, DTEXT_LEFT, DTEXT_TOP,
|
dtext_opt(x + dx, y, l->color, C_NONE, DTEXT_LEFT, DTEXT_TOP,
|
||||||
str, line_length);
|
str, line_length);
|
||||||
|
|
||||||
y += f->line_height + l->line_spacing;
|
y += f->line_distance + l->line_spacing;
|
||||||
str = l->text + l->breaks[i];
|
str = l->text + l->breaks[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ jlist *jlist_create(jlist_item_info_function info_function,
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
jwidget_init(&l->widget, jlist_type_id, parent);
|
jwidget_init(&l->widget, jlist_type_id, parent);
|
||||||
|
jwidget_set_focus_policy(l, J_FOCUS_POLICY_ACCEPT);
|
||||||
|
|
||||||
l->item_count = 0;
|
l->item_count = 0;
|
||||||
l->items = NULL;
|
l->items = NULL;
|
||||||
|
|
45
src/jscene.c
45
src/jscene.c
|
@ -44,13 +44,17 @@ jscene *jscene_create(int x, int y, int w, int h, void *parent)
|
||||||
if(!s) return NULL;
|
if(!s) return NULL;
|
||||||
|
|
||||||
jwidget_init(&s->widget, jscene_type_id, parent);
|
jwidget_init(&s->widget, jscene_type_id, parent);
|
||||||
|
jwidget_set_focus_policy(s, J_FOCUS_POLICY_SCOPE);
|
||||||
jwidget_set_fixed_size(s, w, h);
|
jwidget_set_fixed_size(s, w, h);
|
||||||
jlayout_set_vbox(s);
|
jlayout_set_vbox(s);
|
||||||
|
|
||||||
|
/* The scene is where active focus originates */
|
||||||
|
s->widget.focused = 1;
|
||||||
|
s->widget.active_focused = 1;
|
||||||
|
|
||||||
s->x = x;
|
s->x = x;
|
||||||
s->y = y;
|
s->y = y;
|
||||||
|
|
||||||
s->focus = NULL;
|
|
||||||
s->queue_first = 0;
|
s->queue_first = 0;
|
||||||
s->queue_next = 0;
|
s->queue_next = 0;
|
||||||
s->lost_events = 0;
|
s->lost_events = 0;
|
||||||
|
@ -128,27 +132,37 @@ void jscene_queue_event(jscene *s, jevent e)
|
||||||
|
|
||||||
void *jscene_focused_widget(jscene *s)
|
void *jscene_focused_widget(jscene *s)
|
||||||
{
|
{
|
||||||
return s->focus;
|
jwidget *w = &s->widget;
|
||||||
|
while(w->focus_scope_target)
|
||||||
|
w = w->focus_scope_target;
|
||||||
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jscene_set_focused_widget(jscene *s, void *w0)
|
void jscene_set_focused_widget(jscene *s, void *w0)
|
||||||
{
|
{
|
||||||
J_CAST(w)
|
J_CAST(w)
|
||||||
|
|
||||||
|
/* Unfocus only at the top level */
|
||||||
|
if(!w) {
|
||||||
|
jwidget_scope_set_target(s, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(w->focus_policy == J_FOCUS_POLICY_REJECT)
|
||||||
|
return;
|
||||||
|
|
||||||
/* Check that (s) is an ancestor of (w) */
|
/* Check that (s) is an ancestor of (w) */
|
||||||
if(w) for(jwidget *anc = w; anc != (jwidget *)s; anc = anc->parent) {
|
for(jwidget *anc = w; anc != (jwidget *)s; anc = anc->parent) {
|
||||||
if(anc == NULL) return;
|
if(anc == NULL) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Focus out old focused widget */
|
/* Set targets in every scope along the way up */
|
||||||
if(s->focus) jwidget_event(s->focus,
|
jwidget *scope = w;
|
||||||
(jevent){ .type = JWIDGET_FOCUS_OUT, .source = s->focus });
|
while(scope != NULL) {
|
||||||
|
scope = jwidgetctx_enclosing_focus_scope(scope);
|
||||||
s->focus = w;
|
jwidget_scope_set_target(scope, w);
|
||||||
|
w = scope;
|
||||||
/* Focus in newly-selected widget */
|
}
|
||||||
if(w) jwidget_event(w,
|
|
||||||
(jevent){ .type = JWIDGET_FOCUS_IN, .source = w });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void jscene_show_and_focus(jscene *scene, void *w0)
|
void jscene_show_and_focus(jscene *scene, void *w0)
|
||||||
|
@ -182,12 +196,13 @@ void jscene_show_and_focus(jscene *scene, void *w0)
|
||||||
|
|
||||||
bool jscene_process_key_event(jscene *scene, key_event_t event)
|
bool jscene_process_key_event(jscene *scene, key_event_t event)
|
||||||
{
|
{
|
||||||
jwidget *candidate = scene->focus;
|
jwidget *candidate = jscene_focused_widget(scene);
|
||||||
jevent e = { .type = JWIDGET_KEY, .key = event };
|
jevent e = { .type = JWIDGET_KEY, .key = event };
|
||||||
|
|
||||||
while(candidate) {
|
while(candidate) {
|
||||||
if(jwidget_event(candidate, e)) return true;
|
if(jwidget_event(candidate, e))
|
||||||
candidate = candidate->parent;
|
return true;
|
||||||
|
candidate = jwidgetctx_enclosing_focus_scope(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
138
src/jwidget.c
138
src/jwidget.c
|
@ -31,9 +31,10 @@ static jwidget_poly *widget_types[WIDGET_TYPES_MAX] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Events */
|
/* Events */
|
||||||
uint16_t JWIDGET_KEY;
|
J_DEFINE_EVENTS(
|
||||||
uint16_t JWIDGET_FOCUS_IN;
|
JWIDGET_KEY,
|
||||||
uint16_t JWIDGET_FOCUS_OUT;
|
JWIDGET_FOCUS_CHANGED,
|
||||||
|
JWIDGET_FOCUS_TARGET_CHANGED);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Polymorphic functions for widgets
|
// Polymorphic functions for widgets
|
||||||
|
@ -117,9 +118,13 @@ void jwidget_init(jwidget *w, int type, void *parent)
|
||||||
w->visible = 1;
|
w->visible = 1;
|
||||||
w->floating = 0;
|
w->floating = 0;
|
||||||
w->clipped = 0;
|
w->clipped = 0;
|
||||||
|
w->focus_policy = J_FOCUS_POLICY_REJECT;
|
||||||
|
w->focused = 0;
|
||||||
|
w->active_focused = 0;
|
||||||
|
|
||||||
w->type = type;
|
w->type = type;
|
||||||
w->geometry = NULL;
|
w->geometry = NULL;
|
||||||
|
w->focus_scope_target = NULL;
|
||||||
|
|
||||||
w->x = 0;
|
w->x = 0;
|
||||||
w->y = 0;
|
w->y = 0;
|
||||||
|
@ -605,6 +610,10 @@ void jwidget_set_floating(void *w0, bool floating)
|
||||||
if(w->parent) w->parent->dirty = 1;
|
if(w->parent) w->parent->dirty = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Rendering
|
||||||
|
//---
|
||||||
|
|
||||||
bool jwidget_clipped(void *w0)
|
bool jwidget_clipped(void *w0)
|
||||||
{
|
{
|
||||||
J_CAST(w)
|
J_CAST(w)
|
||||||
|
@ -620,10 +629,6 @@ void jwidget_set_clipped(void *w0, bool clipped)
|
||||||
w->update = 1;
|
w->update = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//---
|
|
||||||
// Rendering
|
|
||||||
//---
|
|
||||||
|
|
||||||
void jwidget_render(void *w0, int x, int y)
|
void jwidget_render(void *w0, int x, int y)
|
||||||
{
|
{
|
||||||
J_CAST(w)
|
J_CAST(w)
|
||||||
|
@ -692,6 +697,117 @@ void jwidget_render(void *w0, int x, int y)
|
||||||
w->update = 0;
|
w->update = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Keyboard focus
|
||||||
|
//---
|
||||||
|
|
||||||
|
void jwidget_set_focus_policy(void *w0, jwidget_focus_policy_t fp)
|
||||||
|
{
|
||||||
|
J_CAST(w)
|
||||||
|
if(w->focus_policy == fp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* If this was a scope, clear it (otherwise the surrounding scope, which is
|
||||||
|
going to expand, could pick up a second, untargeted focused widget). */
|
||||||
|
if(w->focus_policy == J_FOCUS_POLICY_SCOPE)
|
||||||
|
jwidget_scope_clear_focus(w);
|
||||||
|
|
||||||
|
/* Remove focus if we're no longer accepting it */
|
||||||
|
if(fp == J_FOCUS_POLICY_REJECT && jwidget_has_focus(w))
|
||||||
|
jwidgetctx_drop_focus(w);
|
||||||
|
|
||||||
|
w->focus_policy = fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notify_focus_changed(jwidget *w)
|
||||||
|
{
|
||||||
|
jevent e;
|
||||||
|
e.source = w;
|
||||||
|
e.type = JWIDGET_FOCUS_CHANGED;
|
||||||
|
jwidget_event(w, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_focus_chain_active(
|
||||||
|
jwidget *w, bool set, bool notify_toplevel, bool bottom_up)
|
||||||
|
{
|
||||||
|
bool recursive =
|
||||||
|
(w->focus_policy == J_FOCUS_POLICY_SCOPE && w->focus_scope_target);
|
||||||
|
|
||||||
|
if(recursive && bottom_up)
|
||||||
|
set_focus_chain_active(w->focus_scope_target, set, true, bottom_up);
|
||||||
|
|
||||||
|
w->active_focused = set;
|
||||||
|
if(notify_toplevel)
|
||||||
|
notify_focus_changed(w);
|
||||||
|
|
||||||
|
if(recursive && !bottom_up)
|
||||||
|
set_focus_chain_active(w->focus_scope_target, set, true, bottom_up);
|
||||||
|
}
|
||||||
|
|
||||||
|
void jwidget_scope_set_target(void *fs0, void *target0)
|
||||||
|
{
|
||||||
|
J_CAST(fs, target)
|
||||||
|
if(!fs || fs->focus_policy != J_FOCUS_POLICY_SCOPE
|
||||||
|
|| fs->focus_scope_target == target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
jwidget *oldt = fs->focus_scope_target;
|
||||||
|
|
||||||
|
if(oldt) {
|
||||||
|
/* First, if we have active focus, remove it from the entire chain */
|
||||||
|
if(jwidget_has_active_focus(fs))
|
||||||
|
set_focus_chain_active(oldt, false, false, true);
|
||||||
|
|
||||||
|
/* Then, remove the focus flag from the scope target and notify it */
|
||||||
|
oldt->focused = 0;
|
||||||
|
notify_focus_changed(oldt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs->focus_scope_target = target;
|
||||||
|
|
||||||
|
if(target) {
|
||||||
|
/* Now, give focus to the new scope target */
|
||||||
|
target->focused = 1;
|
||||||
|
notify_focus_changed(target);
|
||||||
|
|
||||||
|
if(jwidget_has_active_focus(fs))
|
||||||
|
set_focus_chain_active(target, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
jevent e;
|
||||||
|
e.source = fs;
|
||||||
|
e.type = JWIDGET_FOCUS_TARGET_CHANGED;
|
||||||
|
jwidget_event(fs, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
jwidget *jwidgetctx_enclosing_focus_scope(void *w0)
|
||||||
|
{
|
||||||
|
J_CAST(w)
|
||||||
|
do w = w->parent;
|
||||||
|
while(w && w->focus_policy != J_FOCUS_POLICY_SCOPE);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jwidgetctx_drop_focus(void *w0)
|
||||||
|
{
|
||||||
|
J_CAST(w)
|
||||||
|
if(!w || !jwidget_has_focus(w))
|
||||||
|
return;
|
||||||
|
jwidget *scope = jwidgetctx_enclosing_focus_scope(w);
|
||||||
|
if(scope)
|
||||||
|
jwidget_scope_clear_focus(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
void jwidgetctx_grab_focus(void *w0)
|
||||||
|
{
|
||||||
|
J_CAST(w)
|
||||||
|
if(!w || jwidget_has_focus(w))
|
||||||
|
return;
|
||||||
|
jwidget *scope = jwidgetctx_enclosing_focus_scope(w);
|
||||||
|
if(scope)
|
||||||
|
jwidget_scope_set_target(scope, w);
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Event management
|
// Event management
|
||||||
//---
|
//---
|
||||||
|
@ -752,11 +868,3 @@ int j_register_event(void)
|
||||||
event_id++;
|
event_id++;
|
||||||
return event_id;
|
return event_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((constructor(1000)))
|
|
||||||
static void j_register_jwidget(void)
|
|
||||||
{
|
|
||||||
JWIDGET_KEY = j_register_event();
|
|
||||||
JWIDGET_FOCUS_IN = j_register_event();
|
|
||||||
JWIDGET_FOCUS_OUT = j_register_event();
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue