aboutsummaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-06-24 16:55:18 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-06-26 20:00:27 +0200
commit7c75118429596dcfd86dbefb32e9ae79585c4da0 (patch)
treed06478b8e303d0e2729c57b079f481fbcf5b9adf /src/gui
parentf17c76c4007563389188c147d4e1c766039fb686 (diff)
downloadrts-7c75118429596dcfd86dbefb32e9ae79585c4da0.tar.gz
Revamp gui component
`gui` was tighly coupled to game logic, and could not be extended for other purposes. Therefore, a generic GUI implementation, loosely inspired by well-known GUI frameworks such as GTK, is now provided, with the following properties: - Does not depend on dynamic or static memory allocation, only automatic (i.e., stack) memory allocation required. - Portable among existing implementations. - Simple to extend. - Tiny memory footprint. `gui` is now composed by GUI elements that can be chained to form a tree structure. This is useful e.g.: to calculate X/Y coordinates for a given GUI element given its parent(s). This commit also refactors the older implementation, moving game-specific logic into `player` and making use of the new component.
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/CMakeLists.txt13
-rw-r--r--src/gui/inc/gui.h31
-rw-r--r--src/gui/inc/gui/bar.h39
-rw-r--r--src/gui/inc/gui/button.h44
-rw-r--r--src/gui/inc/gui/label.h30
-rw-r--r--src/gui/inc/gui/progress_bar.h37
-rw-r--r--src/gui/inc/gui/rounded_rect.h43
-rw-r--r--src/gui/privinc/gui_private.h17
-rw-r--r--src/gui/src/bar.c84
-rw-r--r--src/gui/src/button.c167
-rw-r--r--src/gui/src/gui.c455
-rw-r--r--src/gui/src/label.c25
-rw-r--r--src/gui/src/progress_bar.c93
-rw-r--r--src/gui/src/rounded_rect.c224
14 files changed, 862 insertions, 440 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 59ee94d..4c5039b 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -1,3 +1,10 @@
-add_library(gui "src/gui.c")
-target_include_directories(gui PUBLIC "inc")
-target_link_libraries(gui PUBLIC gfx player PRIVATE font unit building)
+add_library(gui
+ "src/bar.c"
+ "src/button.c"
+ "src/gui.c"
+ "src/label.c"
+ "src/progress_bar.c"
+ "src/rounded_rect.c"
+)
+target_include_directories(gui PUBLIC "inc" PRIVATE "privinc")
+target_link_libraries(gui PUBLIC camera gfx peripheral font)
diff --git a/src/gui/inc/gui.h b/src/gui/inc/gui.h
index 254fd47..ec87f45 100644
--- a/src/gui/inc/gui.h
+++ b/src/gui/inc/gui.h
@@ -1,33 +1,30 @@
#ifndef GUI_H
#define GUI_H
+#include <camera.h>
#include <gfx.h>
-#include <human_player.h>
+#include <peripheral.h>
#ifdef __cplusplus
extern "C"
{
#endif
-enum
+struct gui_common
{
- GUI_BAR_LEFT,
- GUI_BAR_MID,
- GUI_BAR_RIGHT,
- GUI_SELECTION_UP_LEFT,
- GUI_SELECTION_UP_RIGHT,
- GUI_SELECTION_MID_VERT,
- GUI_SELECTION_DOWN_LEFT,
- GUI_SELECTION_DOWN_RIGHT,
- GUI_SELECTION_MID,
-
- MAX_GUI_SPRITES
+ int (*update)(struct gui_common *, const union peripheral *,
+ const struct camera *);
+ int (*render)(const struct gui_common *);
+ short x, y;
+ bool centered;
+ struct gui_common *parent, *child, *sibling;
};
-void gui_update(struct human_player *h);
-int gui_render(const struct human_player *h);
-
-extern struct sprite gui_sprites[MAX_GUI_SPRITES];
+void gui_add_child(struct gui_common *parent, struct gui_common *child);
+void gui_add_sibling(struct gui_common *g, struct gui_common *sibling);
+int gui_update(struct gui_common *g, const union peripheral *p,
+ const struct camera *c);
+int gui_render(const struct gui_common *g);
#ifdef __cplusplus
}
diff --git a/src/gui/inc/gui/bar.h b/src/gui/inc/gui/bar.h
new file mode 100644
index 0000000..a8000cf
--- /dev/null
+++ b/src/gui/inc/gui/bar.h
@@ -0,0 +1,39 @@
+#ifndef GUI_BAR_H
+#define GUI_BAR_H
+
+#include <gui.h>
+#include <gfx.h>
+#include <util.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct gui_bar
+{
+ struct gui_common common;
+};
+
+UTIL_STATIC_ASSERT(!offsetof(struct gui_bar, common),
+ "unexpected offset for struct gui_bar");
+
+void gui_bar_init(struct gui_bar *b);
+
+enum
+{
+ GUI_BAR_LEFT,
+ GUI_BAR_MID,
+ GUI_BAR_RIGHT,
+
+ MAX_GUI_BAR_SPRITES
+};
+
+extern struct sprite gui_bar_sprites[MAX_GUI_BAR_SPRITES];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUI_BAR_H */
diff --git a/src/gui/inc/gui/button.h b/src/gui/inc/gui/button.h
new file mode 100644
index 0000000..acfc6c1
--- /dev/null
+++ b/src/gui/inc/gui/button.h
@@ -0,0 +1,44 @@
+#ifndef GUI_BUTTON_H
+#define GUI_BUTTON_H
+
+#include <gfx.h>
+#include <gui.h>
+#include <gui/label.h>
+#include <util.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct gui_button
+{
+ struct gui_common common;
+ struct gui_label label;
+ short w;
+ void *arg;
+ void (*on_pressed)(void *);
+};
+
+UTIL_STATIC_ASSERT(!offsetof(struct gui_button, common),
+ "unexpected offset for struct gui_button");
+
+void gui_button_init(struct gui_button *b);
+
+enum
+{
+ GUI_BUTTON_LEFT,
+ GUI_BUTTON_MID,
+ GUI_BUTTON_RIGHT,
+
+ MAX_GUI_BUTTON_SPRITES
+};
+
+extern struct sprite gui_button_sprites[MAX_GUI_BUTTON_SPRITES];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUI_BUTTON_H */
diff --git a/src/gui/inc/gui/label.h b/src/gui/inc/gui/label.h
new file mode 100644
index 0000000..7a30624
--- /dev/null
+++ b/src/gui/inc/gui/label.h
@@ -0,0 +1,30 @@
+#ifndef GUI_LABEL_H
+#define GUI_LABEL_H
+
+#include <gui.h>
+#include <font.h>
+#include <util.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct gui_label
+{
+ struct gui_common common;
+ enum font font;
+ const char *text;
+};
+
+UTIL_STATIC_ASSERT(!offsetof(struct gui_label, common),
+ "unexpected offset for struct gui_label");
+
+void gui_label_init(struct gui_label *l);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUI_LABEL_H */
diff --git a/src/gui/inc/gui/progress_bar.h b/src/gui/inc/gui/progress_bar.h
new file mode 100644
index 0000000..4fa5129
--- /dev/null
+++ b/src/gui/inc/gui/progress_bar.h
@@ -0,0 +1,37 @@
+#ifndef GUI_PROGRESS_BAR_H
+#define GUI_PROGRESS_BAR_H
+
+#include <gui.h>
+#include <util.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+enum {GUI_PROGRESS_BAR_MAX = 255};
+
+struct gui_progress_bar
+{
+ struct gui_common common;
+ short w, h;
+ bool stp;
+ unsigned char progress;
+ struct
+ {
+ unsigned char r, g, b;
+ } fg, bg;
+};
+
+UTIL_STATIC_ASSERT(!offsetof(struct gui_progress_bar, common),
+ "unexpected offset for struct gui_progress_bar");
+
+void gui_progress_bar_init(struct gui_progress_bar *pb);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUI_PROGRESS_BAR_H */
diff --git a/src/gui/inc/gui/rounded_rect.h b/src/gui/inc/gui/rounded_rect.h
new file mode 100644
index 0000000..2d5a273
--- /dev/null
+++ b/src/gui/inc/gui/rounded_rect.h
@@ -0,0 +1,43 @@
+#ifndef GUI_ROUNDED_RECT_H
+#define GUI_ROUNDED_RECT_H
+
+#include <gui.h>
+#include <gfx.h>
+#include <util.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct gui_rounded_rect
+{
+ struct gui_common common;
+ unsigned short w, h;
+};
+
+UTIL_STATIC_ASSERT(!offsetof(struct gui_rounded_rect, common),
+ "unexpected offset for struct gui_rounded_rect");
+
+void gui_rounded_rect_init(struct gui_rounded_rect *l);
+
+enum
+{
+ GUI_ROUNDED_RECT_UP_LEFT,
+ GUI_ROUNDED_RECT_UP_RIGHT,
+ GUI_ROUNDED_RECT_MID_VERT,
+ GUI_ROUNDED_RECT_DOWN_LEFT,
+ GUI_ROUNDED_RECT_DOWN_RIGHT,
+ GUI_ROUNDED_RECT_MID,
+
+ MAX_GUI_ROUNDED_RECT_SPRITES
+};
+
+extern struct sprite gui_rounded_rect_sprites[MAX_GUI_ROUNDED_RECT_SPRITES];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUI_ROUNDED_RECT_H */
diff --git a/src/gui/privinc/gui_private.h b/src/gui/privinc/gui_private.h
new file mode 100644
index 0000000..af18dcd
--- /dev/null
+++ b/src/gui/privinc/gui_private.h
@@ -0,0 +1,17 @@
+#ifndef GUI_PRIVATE_H
+#define GUI_PRIVATE_H
+
+#include <gui.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void gui_coords(const struct gui_common *g, short *x, short *y);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUI_PRIVATE_H */
diff --git a/src/gui/src/bar.c b/src/gui/src/bar.c
new file mode 100644
index 0000000..708e4d1
--- /dev/null
+++ b/src/gui/src/bar.c
@@ -0,0 +1,84 @@
+#include <gui/bar.h>
+#include <gui.h>
+#include <gfx.h>
+
+struct sprite gui_bar_sprites[MAX_GUI_BAR_SPRITES];
+
+static int render_topleft(void)
+{
+ sprite_get_or_ret(s, -1);
+
+ if (sprite_clone(&gui_bar_sprites[GUI_BAR_LEFT], s))
+ return -1;
+
+ sprite_sort(s);
+ return 0;
+}
+
+static int render_topright(void)
+{
+ sprite_get_or_ret(s, -1);
+
+ if (sprite_clone(&gui_bar_sprites[GUI_BAR_RIGHT], s))
+ return -1;
+
+ s->x = screen_w - s->w;
+ sprite_sort(s);
+ return 0;
+}
+
+static int render_topmid(void)
+{
+ const uint16_t mid_w = gui_bar_sprites[GUI_BAR_MID].w;
+ const uint16_t lw = gui_bar_sprites[GUI_BAR_LEFT].w;
+ const size_t w = screen_w - lw - lw;
+ const size_t rem_mid = w % mid_w;
+ const size_t whole_mid = w / mid_w;
+ const size_t n_mid = rem_mid ? whole_mid + 1 : whole_mid;
+
+ for (struct
+ {
+ size_t i;
+ short x;
+ } a = {.i = 0, .x = lw};
+ a.i < n_mid;
+ a.i++, a.x += mid_w)
+ {
+ sprite_get_or_ret(m, -1);
+
+ if (sprite_clone(&gui_bar_sprites[GUI_BAR_MID], m))
+ return -1;
+
+ m->x = a.x;
+
+ if (rem_mid && a.i + 1 == n_mid)
+ m->w = rem_mid;
+ else
+ m->w = mid_w;
+
+ sprite_sort(m);
+ }
+
+ return 0;
+}
+
+static int render(const struct gui_common *const g)
+{
+ if (render_topleft()
+ || render_topright()
+ || render_topmid())
+ return -1;
+
+ return 0;
+}
+
+void gui_bar_init(struct gui_bar *const b)
+{
+ *b = (const struct gui_bar)
+ {
+ .common =
+ {
+ .render = render
+ }
+ };
+}
diff --git a/src/gui/src/button.c b/src/gui/src/button.c
new file mode 100644
index 0000000..eed744c
--- /dev/null
+++ b/src/gui/src/button.c
@@ -0,0 +1,167 @@
+#include <gui.h>
+#include <gui/button.h>
+#include <gui_private.h>
+#include <camera.h>
+#include <gfx.h>
+#include <mouse.h>
+#include <pad.h>
+#include <peripheral.h>
+
+struct sprite gui_button_sprites[MAX_GUI_BUTTON_SPRITES];
+
+/* Alias for readability. */
+static const struct sprite *const refs = gui_button_sprites;
+
+static int render_left(const struct gui_button *const b,
+ short *const x, const short y)
+{
+ sprite_get_or_ret(s, -1);
+
+ if (sprite_clone(&refs[GUI_BUTTON_LEFT], s))
+ return -1;
+
+ s->x = *x;
+ s->y = y;
+ sprite_sort(s);
+ *x = s->x + s->w;
+ return 0;
+}
+
+static int render_mid(const struct gui_button *const b,
+ short *const x, const short y)
+{
+ const short mid_w = refs[GUI_BUTTON_MID].w;
+ const short lw = refs[GUI_BUTTON_LEFT].w;
+ const short rw = refs[GUI_BUTTON_RIGHT].w;
+ const short w = b->w - lw - rw;
+
+ if (w > 0)
+ {
+ const short rem_mid = w > 0 ? w % mid_w : 0;
+ const short whole_mid = w / mid_w;
+ const short n_mid = rem_mid ? whole_mid + 1 : whole_mid;
+
+ for (struct
+ {
+ size_t i;
+ short x;
+ } a = {.x = lw};
+ a.i < n_mid;
+ a.i++, a.x += mid_w)
+ {
+ sprite_get_or_ret(m, -1);
+
+ if (sprite_clone(&refs[GUI_BUTTON_MID], m))
+ return -1;
+
+ m->x = *x;
+ m->y = y;
+
+ if (rem_mid && a.i + 1 == n_mid)
+ m->w = rem_mid;
+ else
+ m->w = mid_w;
+
+ sprite_sort(m);
+ *x += m->w;
+ }
+ }
+
+ return 0;
+}
+
+static int render_right(const struct gui_button *const b,
+ const short x, const short y)
+{
+ sprite_get_or_ret(s, -1);
+
+ if (sprite_clone(&refs[GUI_BUTTON_RIGHT], s))
+ return -1;
+
+ s->x = x;
+ s->y = y;
+ sprite_sort(s);
+ return 0;
+}
+
+static int render(const struct gui_common *const g)
+{
+ const struct gui_button *const b = (const struct gui_button *)g;
+ short x, y;
+
+ gui_coords(&b->common, &x, &y);
+
+ if (render_left(b, &x, y)
+ || render_mid(b, &x, y)
+ || render_right(b, x, y))
+ return -1;
+
+ return 0;
+}
+
+static bool pressed(const struct gui_button *const b,
+ const union peripheral *const p,
+ const struct camera *const cam)
+{
+ bool check = false;
+
+ switch (p->common.type)
+ {
+ case PERIPHERAL_TYPE_PAD:
+ check = pad_pressed(&p->pad.pad, PAD_KEY_A);
+ break;
+
+ case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
+ check = mouse_justreleased(&p->kbm.mouse, MOUSE_BUTTON_LEFT);
+ break;
+
+ case PERIPHERAL_TYPE_TOUCH:
+ check = mouse_justpressed(&p->kbm.mouse, MOUSE_BUTTON_LEFT);
+ break;
+ }
+
+ if (check)
+ {
+ short x, y;
+
+ gui_coords(&b->common, &x, &y);
+
+ const struct util_rect d =
+ {
+ .x = x,
+ .y = y,
+ .w = b->w,
+ .h = refs[GUI_BUTTON_LEFT].h
+ };
+
+ return cursor_collision(cam, &d);
+ }
+
+ return false;
+}
+
+static int update(struct gui_common *const g,
+ const union peripheral *const p, const struct camera *const c)
+{
+ struct gui_button *const b = (struct gui_button *)g;
+
+ if (pressed(b, p, c) && b->on_pressed)
+ b->on_pressed(b->arg);
+
+ return 0;
+}
+
+void gui_button_init(struct gui_button *const b)
+{
+ *b = (const struct gui_button)
+ {
+ .common =
+ {
+ .update = update,
+ .render = render
+ }
+ };
+
+ gui_label_init(&b->label);
+ gui_add_child(&b->common, &b->label.common);
+}
diff --git a/src/gui/src/gui.c b/src/gui/src/gui.c
index 187e0b4..044820a 100644
--- a/src/gui/src/gui.c
+++ b/src/gui/src/gui.c
@@ -1,452 +1,67 @@
#include <gui.h>
-#include <building.h>
-#include <font.h>
-#include <gfx.h>
-#include <human_player.h>
-#include <player.h>
-#include <unit.h>
-#include <limits.h>
-#include <stddef.h>
-struct sprite gui_sprites[MAX_GUI_SPRITES];
-
-static int render_topleft(void)
-{
- sprite_get_or_ret(s, -1);
-
- if (sprite_clone(&gui_sprites[GUI_BAR_LEFT], s))
- return -1;
-
- sprite_sort(s);
- return 0;
-}
-
-static int render_topright(void)
-{
- sprite_get_or_ret(s, -1);
-
- if (sprite_clone(&gui_sprites[GUI_BAR_RIGHT], s))
- return -1;
-
- s->x = screen_w - s->w;
- sprite_sort(s);
- return 0;
-}
-
-static int render_topmid(void)
-{
- const uint16_t mid_w = gui_sprites[GUI_BAR_MID].w;
- const uint16_t lw = gui_sprites[GUI_BAR_LEFT].w;
- const size_t w = screen_w - lw - lw;
- const size_t rem_mid = w % mid_w;
- const size_t whole_mid = w / mid_w;
- const size_t n_mid = rem_mid ? whole_mid + 1 : whole_mid;
-
- for (struct
- {
- size_t i;
- short x;
- } a = {.i = 0, .x = lw};
- a.i < n_mid;
- a.i++, a.x += mid_w)
- {
- sprite_get_or_ret(m, -1);
-
- if (sprite_clone(&gui_sprites[GUI_BAR_MID], m))
- return -1;
-
- m->x = a.x;
-
- if (rem_mid && a.i + 1 == n_mid)
- m->w = rem_mid;
- else
- m->w = mid_w;
-
- sprite_sort(m);
- }
-
- return 0;
-}
-
-static int render_top(void)
-{
- if (render_topleft()
- || render_topright()
- || render_topmid())
- return -1;
-
- return 0;
-}
-
-static int render_seltop(struct sprite *const up)
-{
- enum {OFFSET = 48};
-
- if (sprite_clone(&gui_sprites[GUI_SELECTION_UP_LEFT], up))
- return -1;
-
- up->y = screen_h - up->h - OFFSET;
- up->x = 16;
- sprite_sort(up);
-
- sprite_get_or_ret(right, -1);
-
- if (sprite_clone(&gui_sprites[GUI_SELECTION_UP_RIGHT], right))
- return -1;
-
- right->x = screen_w - right->w - up->x;
- right->y = up->y;
- sprite_sort(right);
- return 0;
-}
-
-static int render_selvert(const struct sprite *const up, const short x)
+void gui_coords(const struct gui_common *const g, short *const x,
+ short *const y)
{
- const uint16_t vert_h = gui_sprites[GUI_SELECTION_MID_VERT].h;
- const size_t h = screen_h - up->y - up->h - up->h;
- const size_t rem_vert = h % vert_h;
- const size_t whole_vert = h / vert_h;
- const size_t n_vert = rem_vert ? whole_vert + 1 : whole_vert;
+ *x = g->x;
+ *y = g->y;
- for (struct
- {
- size_t i;
- short y;
- } a = {.i = 0, .y = up->y + up->h};
- a.i < n_vert;
- a.i++, a.y += vert_h)
+ for (const struct gui_common *p = g->parent; p; p = p->parent)
{
- sprite_get_or_ret(v, -1);
-
- if (sprite_clone(&gui_sprites[GUI_SELECTION_MID_VERT], v))
- return -1;
-
- v->y = a.y;
- v->x = x;
-
- if (rem_vert && a.i + 1 == n_vert)
- v->h = rem_vert;
- else
- v->h = vert_h;
-
- sprite_sort(v);
+ *x += p->x;
+ *y += p->y;
}
-
- return 0;
-}
-
-static int render_selvertleft(const struct sprite *const up)
-{
- return render_selvert(up, up->x);
}
-static int render_selvertright(const struct sprite *const up)
+void gui_add_sibling(struct gui_common *const g,
+ struct gui_common *const s)
{
- return render_selvert(up, screen_w - gui_sprites[GUI_SELECTION_MID_VERT].w - up->x);
-}
-
-static int render_seldown(const struct sprite *const up)
-{
- {
- sprite_get_or_ret(left, -1);
-
- if (sprite_clone(&gui_sprites[GUI_SELECTION_DOWN_LEFT], left))
- return -1;
-
- left->x = up->x;
- left->y = screen_h - left->h;
- sprite_sort(left);
- }
-
- {
- sprite_get_or_ret(right, -1);
-
- if (sprite_clone(&gui_sprites[GUI_SELECTION_DOWN_RIGHT], right))
- return -1;
-
- right->x = screen_w - right->w - up->x;
- right->y = screen_h - right->h;
- sprite_sort(right);
- }
-
- return 0;
-}
-
-static int render_selrect(const struct sprite *const up)
-{
- rect_get_or_ret(sel, -1);
- semitrans_rect_init(sel);
-
- const struct sprite *const mid = &gui_sprites[GUI_SELECTION_MID],
- *const vert = &gui_sprites[GUI_SELECTION_MID_VERT];
- sel->x = up->x + vert->w;
- sel->y = up->y + mid->h;
- sel->w = screen_w - sel->x - vert->w - up->x;
- sel->h = screen_h - sel->y - mid->h;
- sel->r = 72;
- sel->g = 66;
- sel->b = 56;
- rect_sort(sel);
- return 0;
-}
-
-static int render_selmid(const struct sprite *const up, const short y)
-{
- const uint16_t mid_w = gui_sprites[GUI_SELECTION_MID].w;
- const size_t w = screen_w - up->x - up->x - up->w - up->w;
- const size_t rem_mid = w % mid_w;
- const size_t whole_mid = w / mid_w;
- const size_t n_mid = rem_mid ? whole_mid + 1 : whole_mid;
-
- for (struct
+ for (struct gui_common *c = g; c; c = c->sibling)
+ if (!c->sibling)
{
- size_t i;
- short x;
- } a = {.i = 0, .x = up->x + up->w};
- a.i < n_mid;
- a.i++, a.x += mid_w)
- {
- sprite_get_or_ret(m, -1);
- *m = gui_sprites[GUI_SELECTION_MID];
- m->x = a.x;
- m->y = y;
-
- if (rem_mid && a.i + 1 == n_mid)
- m->w = rem_mid;
- else
- m->w = mid_w;
-
- sprite_sort(m);
- }
-
- return 0;
+ c->sibling = s;
+ break;
+ }
}
-static int render_selmidtop(const struct sprite *const up)
+void gui_add_child(struct gui_common *const p,
+ struct gui_common *const c)
{
- return render_selmid(up, up->y);
-}
+ if (p->child)
+ gui_add_sibling(p->child, c);
+ else
+ p->child = c;
-static int render_selmiddown(const struct sprite *const up)
-{
- return render_selmid(up, screen_h - gui_sprites[GUI_SELECTION_MID].h);
+ c->parent = p;
}
-static int render_topmenu(const struct human_player *const h)
+int gui_update(struct gui_common *const g, const union peripheral *const p,
+ const struct camera *const c)
{
- const struct player *const pl = &h->pl;
-
- if (render_top()
- || (font_printf(FONT, 16, 6, "Wood=%u",
- h->gui_res[RESOURCE_TYPE_WOOD]) < 0)
- || (font_printf(FONT, 108, 6, "Gold=%u",
- h->gui_res[RESOURCE_TYPE_GOLD]) < 0)
- || (font_printf(FONT, 212, 6, "Pop.=%u/%zu",
- pl->pop, sizeof pl->units / sizeof *pl->units) < 0))
+ if (g->update && g->update(g, p, c))
return -1;
- return 0;
-}
-
-static int draw_hp(const struct instance *const i, const short x, const short y,
- const instance_hp max_hp)
-{
- enum {OFFSET = 4, WIDTH = 64, HEIGHT = 4};
- const short ry = y - OFFSET - HEIGHT;
-
- if (ry < 0)
- return 0;
-
- const short gw = (WIDTH * i->hp) / max_hp;
-
- {
- rect_get_or_ret(gr, -1);
- semitrans_rect_init(gr);
- gr->x = x;
- gr->y = ry;
- gr->w = gw;
- gr->h = HEIGHT;
- gr->g = UCHAR_MAX >> 1;
- rect_sort(gr);
- }
-
- {
- rect_get_or_ret(rr, -1);
- semitrans_rect_init(rr);
- rr->x = x + gw;
- rr->y = ry;
- rr->w = WIDTH - gw;
- rr->h = HEIGHT;
- rr->r = UCHAR_MAX >> 1;
- rect_sort(rr);
- }
-
- return 0;
-}
-
-static int draw_miners(const struct resource *const r)
-{
- const struct resource_gold *const g = &r->res.gold;
-
- if (!g->n_miners)
- return 0;
-
- for (size_t i = 0, n = 0; i < sizeof g->miners / sizeof *g->miners; i++)
- {
- if (g->miners[i])
- {
- enum {OFFSET = 112, SZ = 16, GAP = 4};
-
- rect_get_or_ret(r, -1);
- semitrans_rect_init(r);
- r->x = OFFSET + n * (SZ + GAP);
- r->y = screen_h - 40;
- r->r = r->g = r->b = 127;
- r->w = r->h = SZ;
- rect_sort(r);
-
- if (++n >= g->n_miners)
- break;
- }
- }
-
- return 0;
-}
-
-static int draw_carry(const enum font f, const short x, const short y,
- const struct unit *const u)
-{
- if (unit_can_harvest(u))
- {
- const struct unit_harvester *const uh = &u->us.harvester;
+ if (g->child && gui_update(g->child, p, c))
+ return -1;
- if (uh->carry)
- return font_printf(f, x, y, "%d", uh->carry) < 0;
- }
+ for (struct gui_common *s = g->sibling; s; s = s->sibling)
+ if (gui_update(s, p, c))
+ return -1;
return 0;
}
-static int render_selsingle(const struct sprite *const up,
- const struct human_player *const h)
-{
- enum {X_OFF = 8, Y_OFF = 8, HP_Y = 32};
- const short x = up->x + X_OFF, y = up->y + Y_OFF;
-
- for (size_t i = 0; i < sizeof h->sel / sizeof *h->sel; i++)
- {
- const struct sel_instance *const sel = &h->sel[i];
-
- if (sel)
- {
- switch (sel->type)
- {
- case INSTANCE_TYPE_BUILDING:
- {
- const struct building *const b = sel->d.b;
- const struct instance *const in = &b->instance;
- const instance_hp hp = in->hp, max_hp = building_maxhp(b);
-
- if ((font_printf(FONT, x, y, "%s", building_str(b)) < 0)
- || (font_printf(FONT, x, y + HP_Y, "%u/%u", hp, max_hp) < 0)
- || draw_hp(in, x, y + HP_Y - 4, max_hp))
- return -1;
-
- return 0;
- }
- break;
-
- case INSTANCE_TYPE_UNIT:
- {
- const struct unit *const u = sel->d.u;
- const struct instance *const in = &u->instance;
- const instance_hp hp = in->hp, max_hp = unit_maxhp(u);
- enum {CARRY_X = 96, CARRY_Y = 8};
-
- if ((font_printf(FONT, x, y, "%s", unit_str(u)) < 0)
- || (font_printf(FONT, x, y + HP_Y, "%u/%u", hp, max_hp) < 0)
- || draw_carry(FONT, x + CARRY_X, y, u)
- || draw_hp(in, x, y + HP_Y - 4, max_hp))
- return -1;
-
- return 0;
- }
- break;
-
- case INSTANCE_TYPE_RESOURCE:
- {
- const struct resource *const r = sel->d.r;
- const struct instance *const in = &r->instance;
- const instance_hp hp = in->hp, max_hp = resource_maxhp(r);
-
- if ((font_printf(FONT, x, y, "%s", resource_str(r)) < 0)
- || (font_printf(FONT, x, y + HP_Y, "%u/%u", hp, max_hp) < 0)
- || draw_hp(in, x, y + HP_Y - 4, max_hp)
- || (r->type == RESOURCE_TYPE_GOLD && draw_miners(r)))
- return -1;
-
- return 0;
- }
- break;
- }
-
- return 0;
- }
- }
-
- /* Unreachable. */
- return -1;
-}
-
-static int render_selinfo(const struct sprite *const up,
- const struct human_player *const h)
+int gui_render(const struct gui_common *const g)
{
- enum {X_OFF = 8, Y_OFF = 8};
-
- if (h->n_sel == 1)
- return render_selsingle(up, h);
- else if (font_printf(FONT, up->x + X_OFF, up->y + Y_OFF,
- "%u units selected", h->n_sel) < 0)
+ if (g->render && g->render(g))
return -1;
- return 0;
-}
-
-static int render_sel(const struct human_player *const h)
-{
- sprite_get_or_ret(up, -1);
-
- if (render_seltop(up)
- || render_selrect(up)
- || render_selmidtop(up)
- || render_selmiddown(up)
- || render_selvertleft(up)
- || render_selvertright(up)
- || render_seldown(up)
- || render_selinfo(up, h))
+ if (g->child && gui_render(g->child))
return -1;
- return 0;
-}
-
-void gui_update(struct human_player *const h)
-{
- struct player *const pl = &h->pl;
-
- for (size_t i = 0; i < sizeof pl->resources / sizeof *pl->resources; i++)
- {
- if (h->gui_res[i] > pl->resources[i])
- h->gui_res[i]--;
- else if (h->gui_res[i] < pl->resources[i])
- h->gui_res[i]++;
- }
-}
-
-int gui_render(const struct human_player *const h)
-{
- if ((h->top_gui && render_topmenu(h))
- || (h->n_sel && render_sel(h)))
- return -1;
+ for (struct gui_common *s = g->sibling; s; s = s->sibling)
+ if (gui_render(s))
+ return -1;
return 0;
}
diff --git a/src/gui/src/label.c b/src/gui/src/label.c
new file mode 100644
index 0000000..2a24d08
--- /dev/null
+++ b/src/gui/src/label.c
@@ -0,0 +1,25 @@
+#include <gui/label.h>
+#include <gui.h>
+#include <gui_private.h>
+#include <font.h>
+
+static int render(const struct gui_common *const g)
+{
+ const struct gui_label *const l = (const struct gui_label *)g;
+ short x, y;
+
+ gui_coords(g, &x, &y);
+
+ return l->text ? font_puts(l->font, x, y, l->text) : -1;
+}
+
+void gui_label_init(struct gui_label *const l)
+{
+ *l = (const struct gui_label)
+ {
+ .common =
+ {
+ .render = render
+ }
+ };
+}
diff --git a/src/gui/src/progress_bar.c b/src/gui/src/progress_bar.c
new file mode 100644
index 0000000..b836b4d
--- /dev/null
+++ b/src/gui/src/progress_bar.c
@@ -0,0 +1,93 @@
+#include <gui/progress_bar.h>
+#include <gui.h>
+#include <gui_private.h>
+#include <gfx.h>
+
+static short progress_width(const struct gui_progress_bar *const pb)
+{
+ return pb->progress ? ((short)pb->progress * pb->w)
+ / (short)GUI_PROGRESS_BAR_MAX : 0;
+}
+
+static void set_fg_params(const struct gui_progress_bar *const pb,
+ struct rect *const r, const short x, const short y)
+{
+ r->x = x;
+ r->y = y;
+ r->w = progress_width(pb);
+ r->h = pb->h;
+ r->r = pb->fg.r;
+ r->g = pb->fg.g;
+ r->b = pb->fg.b;
+}
+
+static void set_bg_params(const struct gui_progress_bar *const pb,
+ struct rect *const r, const short x, const short y)
+{
+ const short w = progress_width(pb);
+
+ r->x = x + w;
+ r->y = y;
+ r->w = pb->w - w;
+ r->h = pb->h;
+ r->r = pb->bg.r;
+ r->g = pb->bg.g;
+ r->b = pb->bg.b;
+}
+
+static int render_common(const struct gui_progress_bar *const pb,
+ const short x, const short y, void (*init)(struct rect *))
+{
+ {
+ rect_get_or_ret(fg, -1);
+ init(fg);
+ set_fg_params(pb, fg, x, y);
+ rect_sort(fg);
+ }
+
+ {
+ rect_get_or_ret(bg, -1);
+ init(bg);
+ set_bg_params(pb, bg, x, y);
+ rect_sort(bg);
+ }
+
+ return 0;
+}
+
+static int render_op(const struct gui_progress_bar *const pb,
+ const short x, const short y)
+{
+ return render_common(pb, x, y, rect_init);
+}
+
+static int render_stp(const struct gui_progress_bar *const pb,
+ const short x, const short y)
+{
+ return render_common(pb, x, y, semitrans_rect_init);
+}
+
+static int render(const struct gui_common *const g)
+{
+ const struct gui_progress_bar *const pb =
+ (const struct gui_progress_bar *)g;
+ short x, y;
+
+ gui_coords(&pb->common, &x, &y);
+
+ if (pb->stp)
+ return render_stp(pb, x, y);
+
+ return render_op(pb, x, y);
+}
+
+void gui_progress_bar_init(struct gui_progress_bar *const pb)
+{
+ *pb = (const struct gui_progress_bar)
+ {
+ .common =
+ {
+ .render = render
+ }
+ };
+}
diff --git a/src/gui/src/rounded_rect.c b/src/gui/src/rounded_rect.c
new file mode 100644
index 0000000..1ad0902
--- /dev/null
+++ b/src/gui/src/rounded_rect.c
@@ -0,0 +1,224 @@
+#include <gui/rounded_rect.h>
+#include <gui.h>
+#include <gui_private.h>
+#include <gfx.h>
+
+struct sprite gui_rounded_rect_sprites[MAX_GUI_ROUNDED_RECT_SPRITES];
+
+/* Alias for readability. */
+static const struct sprite *const refs = gui_rounded_rect_sprites;
+
+static int render_top(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ {
+ sprite_get_or_ret(left, -1);
+
+ if (sprite_clone(&refs[GUI_ROUNDED_RECT_UP_LEFT], left))
+ return -1;
+
+ left->x = x;
+ left->y = y;
+ sprite_sort(left);
+ }
+
+ {
+ sprite_get_or_ret(right, -1);
+
+ const struct sprite *const ref =
+ &refs[GUI_ROUNDED_RECT_UP_RIGHT];
+
+ if (sprite_clone(ref, right))
+ return -1;
+
+ right->x = x + r->w - ref->w;
+ right->y = y;
+ sprite_sort(right);
+ }
+
+ return 0;
+}
+
+static int render_vert(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ const struct sprite *const vert = &refs[GUI_ROUNDED_RECT_MID_VERT];
+ const short top_h = refs[GUI_ROUNDED_RECT_UP_LEFT].h,
+ h = r->h - (top_h * 2),
+ rem_vert = h % vert->h,
+ whole_vert = h / vert->h,
+ n_vert = rem_vert ? whole_vert + 1 : whole_vert;
+
+ for (struct
+ {
+ size_t i;
+ short y;
+ } a = {.y = y + top_h}; a.i < n_vert; a.i++, a.y += vert->h)
+ {
+ sprite_get_or_ret(v, -1);
+
+ if (sprite_clone(vert, v))
+ return -1;
+
+ v->y = a.y;
+ v->x = x;
+
+ if (rem_vert && a.i + 1 == n_vert)
+ v->h = rem_vert;
+ else
+ v->h = vert->h;
+
+ sprite_sort(v);
+ }
+
+ return 0;
+}
+
+static int render_vertleft(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ return render_vert(r, x, y);
+}
+
+static int render_vertright(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ const struct sprite *const ref = &refs[GUI_ROUNDED_RECT_MID_VERT];
+
+ return render_vert(r, x + r->w - ref->w, y);
+}
+
+static int render_down(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ {
+ const struct sprite *const ref = &refs[GUI_ROUNDED_RECT_DOWN_LEFT];
+ sprite_get_or_ret(left, -1);
+
+ if (sprite_clone(ref, left))
+ return -1;
+
+ left->x = x;
+ left->y = y + r->h - ref->h;
+ sprite_sort(left);
+ }
+
+ {
+ const struct sprite *const ref = &refs[GUI_ROUNDED_RECT_DOWN_RIGHT];
+ sprite_get_or_ret(right, -1);
+
+ if (sprite_clone(ref, right))
+ return -1;
+
+ right->x = x + r->w - ref->w;
+ right->y = y + r->h - ref->h;
+ sprite_sort(right);
+ }
+
+ return 0;
+}
+
+static int render_rect(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ rect_get_or_ret(sel, -1);
+ semitrans_rect_init(sel);
+
+ const struct sprite *const mid = &refs[GUI_ROUNDED_RECT_MID],
+ *const vert = &refs[GUI_ROUNDED_RECT_MID_VERT];
+
+ sel->x = x + vert->w;
+ sel->y = y + mid->h;
+ sel->w = r->w - (vert->w * 2);
+ sel->h = r->h - (mid->h * 2);
+ sel->r = 72;
+ sel->g = 66;
+ sel->b = 56;
+ rect_sort(sel);
+ return 0;
+}
+
+static int render_mid(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ const short mid_w = refs[GUI_ROUNDED_RECT_MID].w,
+ top_w = refs[GUI_ROUNDED_RECT_UP_LEFT].w,
+ w = r->w - (top_w * 2),
+ rem_mid = w % mid_w,
+ whole_mid = w / mid_w,
+ n_mid = rem_mid ? whole_mid + 1 : whole_mid;
+
+ for (struct
+ {
+ size_t i;
+ short x;
+ } a = {.x = x + top_w}; a.i < n_mid; a.i++, a.x += mid_w)
+ {
+ sprite_get_or_ret(m, -1);
+
+ if (sprite_clone(&refs[GUI_ROUNDED_RECT_MID], m))
+ return -1;
+
+ m->x = a.x;
+ m->y = y;
+
+ if (rem_mid && a.i + 1 == n_mid)
+ m->w = rem_mid;
+ else
+ m->w = mid_w;
+
+ sprite_sort(m);
+ }
+
+ return 0;
+}
+
+static int render_midtop(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ return render_mid(r, x, y);
+}
+
+static int render_middown(const struct gui_rounded_rect *const r,
+ const short x, const short y)
+{
+ return render_mid(r, x, y + r->h - refs[GUI_ROUNDED_RECT_MID].h);
+}
+
+static int render(const struct gui_common *const g)
+{
+ const struct gui_rounded_rect *const r = (const struct gui_rounded_rect *)g;
+
+ short x, y;
+
+ gui_coords(&r->common, &x, &y);
+
+ static int (*const steps[])(const struct gui_rounded_rect *const r,
+ const short x, const short y) =
+ {
+ render_top,
+ render_rect,
+ render_midtop,
+ render_middown,
+ render_vertleft,
+ render_vertright,
+ render_down
+ };
+
+ for (size_t i = 0; i < sizeof steps / sizeof *steps; i++)
+ if (steps[i](r, x, y))
+ return -1;
+
+ return 0;
+}
+
+void gui_rounded_rect_init(struct gui_rounded_rect *const r)
+{
+ *r = (const struct gui_rounded_rect)
+ {
+ .common =
+ {
+ .render = render
+ }
+ };
+}