Mercurial > molko
changeset 152:1008a796a9e7
ui: make UI widgets usable as actions
While here add a example-ui program with move support.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 15 Oct 2020 18:09:45 +0200 |
parents | b19d076856d2 |
children | aa6e70e330a1 |
files | doxygen/groups.c examples/example-ui.c libui/ui/button.c libui/ui/button.h libui/ui/checkbox.c libui/ui/checkbox.h libui/ui/frame.c libui/ui/frame.h libui/ui/label.c libui/ui/label.h libui/ui/theme.h |
diffstat | 11 files changed, 376 insertions(+), 64 deletions(-) [+] |
line wrap: on
line diff
--- a/doxygen/groups.c Thu Oct 15 14:01:24 2020 +0200 +++ b/doxygen/groups.c Thu Oct 15 18:09:45 2020 +0200 @@ -40,3 +40,8 @@ * \defgroup input Input and events * \brief Input and event handling. */ + +/** + * \defgroup ui UI + * \brief User interface elements. + */
--- a/examples/example-ui.c Thu Oct 15 14:01:24 2020 +0200 +++ b/examples/example-ui.c Thu Oct 15 18:09:45 2020 +0200 @@ -16,8 +16,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <core/action.h> #include <core/clock.h> #include <core/event.h> +#include <core/maths.h> #include <core/panic.h> #include <core/painter.h> #include <core/sys.h> @@ -30,53 +32,101 @@ #include <ui/label.h> #include <ui/theme.h> -#define W (1280) -#define H (720) +#define W (1280) +#define H (720) -static struct frame frame = { - .x = 10, - .y = 10, - .w = 400, - .h = 200 -}; +#define FRAME_ORIGIN_X (10) +#define FRAME_ORIGIN_Y (10) +#define FRAME_WIDTH (400) +#define FRAME_HEIGHT (200) + +#define HEADER_HEIGHT (32) + +#define ELEMENT_HEIGHT (20) -static struct label title = { - .text = "Preferences", - .x = 10, - .y = 10, - .w = 400, - .h = 200, - .flags = LABEL_FLAGS_SHADOW, - .align = ALIGN_TOP_LEFT -}; - +/* + * We design a basic UI like this. + * + * FRAME_WIDTH + * +---------------------------------------------+-- + * | Title | | HEADER_HEIGHT + * +---------------------------------------------+-- + * | [x] Auto save | + * | | + * | | + * | [ Quit ] | + * +---------------------------------------------+ + */ static struct { - struct checkbox cb; - struct label label; -} autosave = { - .cb = { - .x = 20, - .y = 60, - .w = 20, - .h = 20 + struct action_stack st; + + struct { + bool active; + int x; + int y; + } motion; + + struct { + struct frame frame; + struct action act; + } panel; + + struct { + struct label label; + struct action act; + } header; + + struct { + struct checkbox cb; + struct action cb_act; + struct label label; + struct action label_act; + } autosave; + + struct { + struct button button; + struct action act; + } quit; +} ui = { + .panel = { + .frame = { + .x = FRAME_ORIGIN_X, + .y = FRAME_ORIGIN_Y, + .w = FRAME_WIDTH, + .h = FRAME_HEIGHT + } }, - .label = { - .text = "Auto save game", - .x = 20 + 20, - .y = 60, - .w = 200, - .h = 20, - .align = ALIGN_LEFT, - .flags = LABEL_FLAGS_SHADOW + .header = { + .label = { + .text = "Preferences", + .x = FRAME_ORIGIN_X, + .y = FRAME_ORIGIN_Y, + .w = FRAME_WIDTH, + .h = HEADER_HEIGHT, + .flags = LABEL_FLAGS_SHADOW, + .align = ALIGN_LEFT + } + }, + .autosave = { + .cb = { + .w = ELEMENT_HEIGHT, + .h = ELEMENT_HEIGHT + }, + .label = { + .text = "Auto save game", + .align = ALIGN_LEFT, + .flags = LABEL_FLAGS_SHADOW, + .h = ELEMENT_HEIGHT + } + }, + .quit = { + .button = { + .text = "Quit", + .h = ELEMENT_HEIGHT + } } }; -static struct button btquit = { - .text = "Quit", - .w = 50, - .h = 20 -}; - static void init(void) { @@ -87,13 +137,85 @@ } static void +resize(void) +{ + const unsigned int padding = theme_default()->padding; + + /* Header. */ + ui.header.label.x = ui.panel.frame.x; + ui.header.label.y = ui.panel.frame.y; + + /* Auto save. */ + ui.autosave.cb.x = ui.panel.frame.x + padding; + ui.autosave.cb.y = ui.panel.frame.y + HEADER_HEIGHT + padding; + ui.autosave.label.w = ui.panel.frame.w - ui.autosave.cb.w - padding; + ui.autosave.label.x = ui.autosave.cb.x + ui.autosave.cb.w; + ui.autosave.label.y = ui.autosave.cb.y; + + /* Button. */ + ui.quit.button.w = ui.panel.frame.w / 4; + + align( + ALIGN_BOTTOM_RIGHT, + &ui.quit.button.x, + &ui.quit.button.y, + ui.quit.button.w, + ui.quit.button.h, + ui.panel.frame.x + padding, + ui.panel.frame.y + padding, + ui.panel.frame.w - padding * 2, + ui.panel.frame.h - padding * 2 + ); +} + +static void +prepare(void) +{ + /* Frame. */ + frame_action(&ui.panel.frame, &ui.panel.act); + + /* Header title. */ + label_action(&ui.header.label, &ui.header.act); + + /* Button quit. */ + button_action(&ui.quit.button, &ui.quit.act); + + /* Autosave. */ + checkbox_action(&ui.autosave.cb, &ui.autosave.cb_act); + label_action(&ui.autosave.label, &ui.autosave.label_act); + + /* Add all UI elements. */ + action_stack_add(&ui.st, &ui.panel.act); + action_stack_add(&ui.st, &ui.header.act); + action_stack_add(&ui.st, &ui.autosave.cb_act); + action_stack_add(&ui.st, &ui.autosave.label_act); + action_stack_add(&ui.st, &ui.quit.act); +} + +static bool +headerclick(int x, int y) +{ + return maths_is_boxed( + ui.panel.frame.x, + ui.panel.frame.y, + ui.panel.frame.w, + HEADER_HEIGHT, + x, + y + ); +} + +static void run(void) { struct clock clock = {0}; clock_start(&clock); - for (;;) { + prepare(); + resize(); + + while (ui.quit.button.state != BUTTON_STATE_ACTIVATED) { unsigned int elapsed = clock_elapsed(&clock); clock_start(&clock); @@ -102,31 +224,37 @@ switch (ev.type) { case EVENT_QUIT: return; + case EVENT_MOUSE: + if (ui.motion.active) { + ui.panel.frame.x += ev.mouse.x - ui.motion.x; + ui.panel.frame.y += ev.mouse.y - ui.motion.y; + ui.motion.x = ev.mouse.x; + ui.motion.y = ev.mouse.y; + resize(); + } + break; + case EVENT_CLICKDOWN: + if (headerclick(ev.click.x, ev.click.y)) { + ui.motion.active = true; + ui.motion.x = ev.click.x; + ui.motion.y = ev.click.y; + } + else + action_stack_handle(&ui.st, &ev); + break; + case EVENT_CLICKUP: + ui.motion.active = false; + /* Fallthrough. */ default: - checkbox_handle(&autosave.cb, &ev); - button_handle(&btquit, &ev); - - if (btquit.state == BUTTON_STATE_ACTIVATED) - return; - + action_stack_handle(&ui.st, &ev); break; } } - /* Compute button position at runtime. */ - align(ALIGN_BOTTOM_RIGHT, &btquit.x, &btquit.y, btquit.w, btquit.h, - frame.x, frame.y, frame.w, frame.h); - - btquit.x -= theme_default()->padding; - btquit.y -= theme_default()->padding; - painter_set_color(0xffffffff); painter_clear(); - frame_draw(&frame); - label_draw(&title); - checkbox_draw(&autosave.cb); - label_draw(&autosave.label); - button_draw(&btquit); + action_stack_update(&ui.st, elapsed); + action_stack_draw(&ui.st); painter_present(); if ((elapsed = clock_elapsed(&clock)) < 20) @@ -154,4 +282,3 @@ return 0; } -
--- a/libui/ui/button.c Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/button.c Thu Oct 15 18:09:45 2020 +0200 @@ -17,11 +17,14 @@ */ #include <assert.h> +#include <string.h> +#include <core/action.h> #include <core/event.h> #include <core/maths.h> #include <core/painter.h> +#include "align.h" #include "button.h" #include "label.h" #include "theme.h" @@ -37,6 +40,18 @@ click->x, click->y); } +static void +handle(struct action *act, const union event *ev) +{ + button_handle(act->data, ev); +} + +static void +draw(struct action *act) +{ + button_draw(act->data); +} + void button_draw_default(struct theme *t, const struct button *button) { @@ -102,3 +117,15 @@ theme_draw_button(button->theme, button); } + +void +button_action(struct button *button, struct action *act) +{ + assert(button); + assert(act); + + memset(act, 0, sizeof (*act)); + act->data = button; + act->handle = handle; + act->draw = draw; +}
--- a/libui/ui/button.h Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/button.h Thu Oct 15 18:09:45 2020 +0200 @@ -22,10 +22,12 @@ /** * \file button.h * \brief GUI button. + * \ingroup ui */ union event; +struct action; struct theme; /** @@ -93,4 +95,26 @@ void button_draw(struct button *button); +/** + * Convert the button into an action. + * + * The following field will be set into the action: + * + * - act->data: points to button (reference), + * - act->handle: a wrapper to button_handle, + * - act->draw: a wrapper to button_draw. + * + * The button being an UI element is considered to never completes, as such + * you will need to handle this case or to use a custom update function. + * + * \note You will still need to check the button state and reset it at some + * point. + * \pre button != NULL + * \pre act != NULL + * \param button the button to reference + * \param act the action to fill + */ +void +button_action(struct button *button, struct action *act); + #endif /* !MOLKO_BUTTON_H */
--- a/libui/ui/checkbox.c Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/checkbox.c Thu Oct 15 18:09:45 2020 +0200 @@ -17,7 +17,9 @@ */ #include <assert.h> +#include <string.h> +#include <core/action.h> #include <core/event.h> #include <core/maths.h> #include <core/painter.h> @@ -35,6 +37,18 @@ return maths_is_boxed(cb->x, cb->y, cb->w, cb->h, click->x, click->y); } +static void +handle(struct action *act, const union event *ev) +{ + checkbox_handle(act->data, ev); +} + +static void +draw(struct action *act) +{ + checkbox_draw(act->data); +} + void checkbox_draw_default(struct theme *t, const struct checkbox *cb) { @@ -73,3 +87,15 @@ { theme_draw_checkbox(cb->theme, cb); } + +void +checkbox_action(struct checkbox *cb, struct action *act) +{ + assert(cb); + assert(act); + + memset(act, 0, sizeof (*act)); + act->data = cb; + act->handle = handle; + act->draw = draw; +}
--- a/libui/ui/checkbox.h Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/checkbox.h Thu Oct 15 18:09:45 2020 +0200 @@ -22,12 +22,16 @@ /** * \file checkbox.h * \brief GUI checkbox. + * \ingroup ui */ #include <stdbool.h> union event; +struct action; +struct theme; + /** * \brief GUI checkbox. */ @@ -71,4 +75,26 @@ void checkbox_draw(const struct checkbox *cb); +/** + * Convert the checkbox into an action. + * + * The following field will be set into the action: + * + * - act->data: points to cb (reference), + * - act->handle: a wrapper to checkbox_handle, + * - act->draw: a wrapper to checkbox_draw. + * + * The checkbox being an UI element is considered to never completes, as such + * you will need to handle this case or to use a custom update function. + * + * \note You will still need to check the checkbox state and reset it at some + * point. + * \pre cb != NULL + * \pre act != NULL + * \param cb the checkbox to reference + * \param act the action to fill + */ +void +checkbox_action(struct checkbox *cb, struct action *act); + #endif /* !MOLKO_CHECKBOX_H */
--- a/libui/ui/frame.c Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/frame.c Thu Oct 15 18:09:45 2020 +0200 @@ -17,12 +17,20 @@ */ #include <assert.h> +#include <string.h> +#include <core/action.h> #include <core/painter.h> #include "frame.h" #include "theme.h" +static void +draw(struct action *act) +{ + frame_draw(act->data); +} + void frame_draw_default(struct theme *t, const struct frame *frame) { @@ -46,3 +54,14 @@ theme_draw_frame(frame->theme, frame); } + +void +frame_action(struct frame *frame, struct action *act) +{ + assert(frame); + assert(act); + + memset(act, 0, sizeof (*act)); + act->data = frame; + act->draw = draw; +}
--- a/libui/ui/frame.h Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/frame.h Thu Oct 15 18:09:45 2020 +0200 @@ -22,8 +22,10 @@ /** * \file frame.h * \brief GUI frame. + * \ingroup ui */ +struct action; struct theme; /** @@ -66,4 +68,23 @@ void frame_draw(const struct frame *frame); +/** + * Convert the frame into an action. + * + * The following field will be set into the action: + * + * - act->data: points to frame (reference), + * - act->draw: a wrapper to checkbox_draw. + * + * The frame being an UI element is considered to never completes, as such + * you will need to handle this case or to use a custom update function. + * + * \pre frame != NULL + * \pre act != NULL + * \param frame the frame to reference + * \param act the action to fill + */ +void +frame_action(struct frame *frame, struct action *act); + #endif /* !MOLKO_FRAME_H */
--- a/libui/ui/label.c Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/label.c Thu Oct 15 18:09:45 2020 +0200 @@ -17,15 +17,22 @@ */ #include <assert.h> +#include <string.h> +#include <core/action.h> #include <core/font.h> #include <core/panic.h> -#include <core/trace.h> #include <core/texture.h> #include "label.h" #include "theme.h" +static void +draw(struct action *act) +{ + label_draw(act->data); +} + void label_draw_default(struct theme *t, const struct label *label) { @@ -83,8 +90,16 @@ assert(label); assert(label->text); - if (label->align != ALIGN_NONE && (label->w == 0 || label->h == 0)) - trace("label %p has alignment but null dimensions", label); - theme_draw_label(label->theme, label); } + +void +label_action(struct label *label, struct action *act) +{ + assert(label); + assert(act); + + memset(act, 0, sizeof (*act)); + act->data = label; + act->draw = draw; +}
--- a/libui/ui/label.h Thu Oct 15 14:01:24 2020 +0200 +++ b/libui/ui/label.h Thu Oct 15 18:09:45 2020 +0200 @@ -22,10 +22,12 @@ /** * \file label.h * \brief GUI label. + * \ingroup ui */ #include "align.h" +struct action; struct theme; /** @@ -74,4 +76,23 @@ void label_draw(const struct label *label); +/** + * Convert the label into an action. + * + * The following field will be set into the action: + * + * - act->data: points to label (reference), + * - act->draw: a wrapper to label_draw. + * + * The label being an UI element is considered to never completes, as such + * you will need to handle this case or to use a custom update function. + * + * \pre frame != NULL + * \pre act != NULL + * \param label the label to reference + * \param act the action to fill + */ +void +label_action(struct label *label, struct action *act); + #endif /* !MOLKO_LABEL_H */