aboutsummaryrefslogtreecommitdiff
path: root/src/player
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/player
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/player')
-rw-r--r--src/player/CMakeLists.txt8
-rw-r--r--src/player/privinc/human_player_private.h18
-rw-r--r--src/player/src/human_player.c5
-rw-r--r--src/player/src/human_player_gui.c341
4 files changed, 368 insertions, 4 deletions
diff --git a/src/player/CMakeLists.txt b/src/player/CMakeLists.txt
index 6d692fe..3d995ee 100644
--- a/src/player/CMakeLists.txt
+++ b/src/player/CMakeLists.txt
@@ -1,5 +1,9 @@
-add_library(player "src/player.c" "src/human_player.c")
-target_include_directories(player PUBLIC "inc")
+add_library(player
+ "src/player.c"
+ "src/human_player.c"
+ "src/human_player_gui.c"
+)
+target_include_directories(player PUBLIC "inc" PRIVATE "privinc")
target_link_libraries(player
PUBLIC
building
diff --git a/src/player/privinc/human_player_private.h b/src/player/privinc/human_player_private.h
new file mode 100644
index 0000000..5d8a557
--- /dev/null
+++ b/src/player/privinc/human_player_private.h
@@ -0,0 +1,18 @@
+#ifndef HUMAN_PLAYER_PRIVATE_H
+#define HUMAN_PLAYER_PRIVATE_H
+
+#include <human_player.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void human_player_gui_update(struct human_player *h);
+int human_player_gui_render(const struct human_player *h);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HUMAN_PLAYER_PRIVATE_H */
diff --git a/src/player/src/human_player.c b/src/player/src/human_player.c
index 1b77105..221146d 100644
--- a/src/player/src/human_player.c
+++ b/src/player/src/human_player.c
@@ -1,4 +1,5 @@
#include <human_player.h>
+#include <human_player_private.h>
#include <player.h>
#include <building.h>
#include <camera.h>
@@ -595,7 +596,7 @@ void human_player_update(struct human_player *const h,
if (p->alive)
{
- gui_update(h);
+ human_player_gui_update(h);
update_selected(h);
update_target(h);
peripheral_update(&h->periph);
@@ -687,7 +688,7 @@ int human_player_render(const struct human_player *const h,
|| render_own_units(h)
|| render_own_buildings(h)
|| render_resources(h, o->res, o->n_res)
- || gui_render(h))
+ || human_player_gui_render(h))
return -1;
switch (h->periph.common.type)
diff --git a/src/player/src/human_player_gui.c b/src/player/src/human_player_gui.c
new file mode 100644
index 0000000..93228c8
--- /dev/null
+++ b/src/player/src/human_player_gui.c
@@ -0,0 +1,341 @@
+#include <building.h>
+#include <gfx.h>
+#include <human_player.h>
+#include <player.h>
+#include <unit.h>
+#include <gui.h>
+#include <gui/bar.h>
+#include <gui/label.h>
+#include <gui/progress_bar.h>
+#include <gui/rounded_rect.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+
+enum {X_OFF = 8, Y_OFF = 8, HP_Y = 32};
+
+static int draw_hp(const struct instance *const i, const instance_hp max_hp,
+ struct gui_common *const r)
+{
+ enum {WIDTH = 64, HEIGHT = 4};
+ struct gui_progress_bar pb;
+
+ gui_progress_bar_init(&pb);
+ pb.common.x = X_OFF;
+ pb.common.y = HP_Y - 8;
+ pb.progress = ((unsigned)GUI_PROGRESS_BAR_MAX * i->hp) / max_hp;
+ pb.fg.g = UCHAR_MAX >> 1;
+ pb.bg.r = UCHAR_MAX >> 1;
+ pb.w = WIDTH;
+ pb.h = HEIGHT;
+ pb.stp = true;
+ gui_add_child(r, &pb.common);
+
+ return gui_render(r);
+}
+
+#if 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;
+}
+#endif
+
+void human_player_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]++;
+ }
+}
+
+static int render_sel_single_building(const struct human_player *const h,
+ const struct sel_instance *const sel, struct gui_common *const r)
+{
+ 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);
+ struct gui_label bl;
+
+ gui_label_init(&bl);
+ bl.common.x = X_OFF;
+ bl.common.y = Y_OFF;
+ bl.text = building_str(b);
+ gui_add_child(r, &bl.common);
+
+ char hp_str[sizeof "65535/65535"];
+
+ const int rs = snprintf(hp_str, sizeof hp_str, "%u/%u", hp, max_hp);
+
+ if (rs < 0 || rs >= sizeof hp_str)
+ return -1;
+
+ struct gui_label hpl;
+
+ gui_label_init(&hpl);
+ hpl.common.x = X_OFF;
+ hpl.common.y = HP_Y;
+ hpl.text = hp_str;
+ gui_add_child(r, &hpl.common);
+
+ return draw_hp(in, max_hp, r);
+}
+
+static int render_sel_single_unit(const struct human_player *const h,
+ const struct sel_instance *const sel, struct gui_common *const r)
+{
+ 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};
+
+ struct gui_label ul;
+
+ gui_label_init(&ul);
+ ul.common.x = X_OFF;
+ ul.common.y = Y_OFF;
+ ul.text = unit_str(u);
+ gui_add_child(r, &ul.common);
+
+ char hp_str[sizeof "65535/65535"];
+
+ const int rs = snprintf(hp_str, sizeof hp_str, "%u/%u", hp, max_hp);
+
+ if (rs < 0 || rs >= sizeof hp_str)
+ return -1;
+
+ struct gui_label hpl;
+
+ gui_label_init(&hpl);
+ hpl.common.x = X_OFF;
+ hpl.common.y = HP_Y;
+ hpl.text = hp_str;
+ gui_add_child(r, &hpl.common);
+
+ if (unit_can_harvest(u))
+ {
+ const struct unit_harvester *const uh = &u->us.harvester;
+
+ if (uh->carry)
+ {
+ char c_str[sizeof "255"];
+ const int rs = snprintf(c_str, sizeof c_str, "%hhu", uh->carry);
+ struct gui_label cl;
+
+ gui_label_init(&cl);
+ cl.common.x = CARRY_X;
+ cl.common.y = CARRY_Y;
+ cl.text = c_str;
+ gui_add_child(r, &cl.common);
+
+ if (rs < 0 || rs >= sizeof c_str)
+ return -1;
+
+ return gui_render(r);
+ }
+ }
+
+ return draw_hp(in, max_hp, r);
+}
+
+static int render_sel_single_resource(const struct human_player *const h,
+ const struct sel_instance *const sel, struct gui_common *const r)
+{
+ const struct resource *const res = sel->d.r;
+ const struct instance *const in = &res->instance;
+ const instance_hp hp = in->hp, max_hp = resource_maxhp(res);
+ struct gui_label rl;
+
+ gui_label_init(&rl);
+ rl.common.x = X_OFF;
+ rl.common.y = Y_OFF;
+ rl.text = resource_str(res);
+ gui_add_child(r, &rl.common);
+
+ char hp_str[sizeof "65535/65535"];
+ const int rs = snprintf(hp_str, sizeof hp_str, "%u/%u", hp, max_hp);
+
+ if (rs < 0 || rs >= sizeof hp_str)
+ return -1;
+
+ struct gui_label hpl;
+
+ gui_label_init(&hpl);
+ hpl.common.x = X_OFF;
+ hpl.common.y = HP_Y;
+ hpl.text = hp_str;
+ gui_add_child(r, &hpl.common);
+
+ return draw_hp(in, max_hp, r);
+}
+
+static int render_sel_single(const struct human_player *const h,
+ struct gui_common *const r)
+{
+ 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:
+ return render_sel_single_building(h, sel, r);
+
+ case INSTANCE_TYPE_UNIT:
+ return render_sel_single_unit(h, sel, r);
+
+ case INSTANCE_TYPE_RESOURCE:
+ return render_sel_single_resource(h, sel, r);
+ }
+ }
+ }
+
+ /* Unreachable. */
+ return -1;
+}
+
+static int render_sel_multiple(const struct human_player *const h,
+ struct gui_common *const r)
+{
+ struct gui_label l;
+ char str[sizeof "4294967295 units selected"];
+ const int rs = snprintf(str, sizeof str, "%zu units selected", h->n_sel);
+
+ if (rs < 0 || rs >= sizeof str)
+ return -1;
+
+ gui_label_init(&l);
+ l.common.x = X_OFF;
+ l.common.y = Y_OFF;
+ l.text = str;
+ gui_add_child(r, &l.common);
+
+ return gui_render(r);
+}
+
+static int render_sel(const struct human_player *const h)
+{
+ enum {OFFSET = 60};
+ struct gui_rounded_rect r;
+
+ gui_rounded_rect_init(&r);
+ r.common.x = 16;
+ r.common.y = screen_h - OFFSET;
+ r.w = screen_w - (r.common.x * 2);
+ r.h = OFFSET;
+
+ if (h->n_sel == 1)
+ return render_sel_single(h, &r.common);
+
+ return render_sel_multiple(h, &r.common);
+}
+
+static int render_top(const struct human_player *const h)
+{
+ const struct player *const pl = &h->pl;
+ struct gui_bar b;
+
+ gui_bar_init(&b);
+ char wood_str[sizeof "Wood=429496729"];
+
+ {
+ const int rs = snprintf(wood_str, sizeof wood_str,
+ "Wood=%" PRIu32, h->gui_res[RESOURCE_TYPE_WOOD]);
+
+ if (rs < 0 || rs >= sizeof wood_str)
+ return -1;
+ }
+
+ struct gui_label wl;
+
+ gui_label_init(&wl);
+ wl.common.x = 16;
+ wl.common.y = 6;
+ wl.text = wood_str;
+ gui_add_child(&b.common, &wl.common);
+
+ char gold_str[sizeof "Gold=429496729"];
+
+ {
+ const int rs = snprintf(gold_str, sizeof gold_str,
+ "Gold=%" PRIu32, h->gui_res[RESOURCE_TYPE_GOLD]);
+
+ if (rs < 0 || rs >= sizeof wood_str)
+ return -1;
+ }
+
+ struct gui_label gl;
+
+ gui_label_init(&gl);
+ gl.common.x = 108;
+ gl.common.y = 6;
+ gl.text = gold_str;
+ gui_add_child(&b.common, &gl.common);
+
+ char pop_str[sizeof "Pop.=255/255"];
+
+ {
+ const int rs = snprintf(pop_str, sizeof pop_str,
+ "Pop.=%hhu/%zu", pl->pop, sizeof pl->units / sizeof *pl->units);
+
+ if (rs < 0 || rs >= sizeof wood_str)
+ return -1;
+ }
+
+ struct gui_label popl;
+
+ gui_label_init(&popl);
+ popl.common.x = 212;
+ popl.common.y = 6;
+ popl.text = pop_str;
+ gui_add_child(&b.common, &popl.common);
+
+ if (gui_render(&b.common))
+ return -1;
+
+ return 0;
+}
+
+int human_player_gui_render(const struct human_player *const h)
+{
+ if (h->top_gui && render_top(h))
+ return -1;
+
+ if (h->n_sel && render_sel(h))
+ return -1;
+
+ return 0;
+}