aboutsummaryrefslogtreecommitdiff
path: root/src/menu
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-09-27 17:03:06 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-11-01 16:26:16 +0100
commit980858186149651df5543b6fc99a4f7db0cdd089 (patch)
treed347200b0a562d84df505097651ad0642f207fdd /src/menu
parent39f50e601d395bbd2d78d0147ac530b756da2fff (diff)
downloadjancity-980858186149651df5543b6fc99a4f7db0cdd089.tar.gz
WIP
Diffstat (limited to 'src/menu')
-rw-r--r--src/menu/CMakeLists.txt16
-rw-r--r--src/menu/privinc/menu_net.h62
-rw-r--r--src/menu/privinc/menu_private.h11
-rw-r--r--src/menu/src/common.c11
-rw-r--r--src/menu/src/gamecfg_menu.c735
-rw-r--r--src/menu/src/host_menu.c159
-rw-r--r--src/menu/src/hostjoin_menu.c129
-rw-r--r--src/menu/src/join_menu.c334
-rw-r--r--src/menu/src/main_menu.c24
-rw-r--r--src/menu/src/menu.c10
-rw-r--r--src/menu/src/menu_net.c447
-rw-r--r--src/menu/src/settings_menu.c396
12 files changed, 1977 insertions, 357 deletions
diff --git a/src/menu/CMakeLists.txt b/src/menu/CMakeLists.txt
index 465250f..9780148 100644
--- a/src/menu/CMakeLists.txt
+++ b/src/menu/CMakeLists.txt
@@ -1,9 +1,23 @@
add_library(menu
"src/gamecfg_menu.c"
"src/hostjoin_menu.c"
+ "src/host_menu.c"
"src/join_menu.c"
"src/menu.c"
+ "src/menu_net.c"
"src/main_menu.c"
+ "src/settings_menu.c"
)
target_include_directories(menu PUBLIC "inc" PRIVATE "privinc")
-target_link_libraries(menu PRIVATE camera game gfx gui input system)
+target_link_libraries(menu PRIVATE
+ camera
+ game
+ gfx
+ gui
+ input
+ net
+ packet
+ settings
+ system
+ util
+)
diff --git a/src/menu/privinc/menu_net.h b/src/menu/privinc/menu_net.h
new file mode 100644
index 0000000..1980917
--- /dev/null
+++ b/src/menu/privinc/menu_net.h
@@ -0,0 +1,62 @@
+#ifndef MENU_NETCFG_H
+#define MENU_NETCFG_H
+
+#include <gui.h>
+#include <gui/button.h>
+#include <gui/checkbox.h>
+#include <gui/container.h>
+#include <gui/label.h>
+#include <gui/line_edit.h>
+#include <net.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct menu_net_gui
+{
+ char device[32], address[32], port[sizeof "65535"];
+ bool update_domain;
+ struct gui_container cnt;
+ struct gui_label type;
+ struct gui_button type_btn;
+ enum net_domain domain;
+
+ union
+ {
+ struct gui_container cnt;
+
+ struct menu_ipv4_gui
+ {
+ struct gui_container cnt, relay_cnt, announce_cnt;
+ struct gui_button next_page_btn;
+ struct gui_label address, port, relay, announce;
+ struct gui_line_edit address_le, port_le, announce_le;
+ struct gui_checkbox relay_ch, announce_ch;
+ } ipv4;
+
+ struct menu_serial_gui
+ {
+ struct gui_container cnt, hw_ctrl_cnt;
+ struct gui_label device, speed, hw_ctrl;
+ struct gui_button speed_btn;
+ struct gui_line_edit device_le;
+ struct gui_checkbox hw_ctrl_ch;
+ size_t speed_i;
+ } serial;
+ } gui;
+};
+
+int menu_net_gui_init(struct menu_net_gui *m);
+void menu_net_gui_update(struct menu_net_gui *m);
+int menu_net_server(const struct menu_net_gui *m, union net_server *s);
+int menu_net_connect(const struct menu_net_gui *m, union net_connect *c);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MENU_NETCFG_H */
diff --git a/src/menu/privinc/menu_private.h b/src/menu/privinc/menu_private.h
index 181825c..d8aa089 100644
--- a/src/menu/privinc/menu_private.h
+++ b/src/menu/privinc/menu_private.h
@@ -3,8 +3,10 @@
#include <camera.h>
#include <input.h>
+#include <net.h>
#include <peripheral.h>
-#include <stdbool.h>
+#include <settings.h>
+#include <stddef.h>
#ifdef __cplusplus
extern "C"
@@ -16,6 +18,7 @@ struct menu_common
struct camera cam;
union peripheral p;
struct input in;
+ struct settings s;
};
int menu_update(struct menu_common *c,
@@ -23,9 +26,11 @@ int menu_update(struct menu_common *c,
int (*render)(const struct menu_common *, void *),
void *arg);
int menu_main(struct menu_common *c);
-int menu_hostjoin(struct menu_common *c, bool *back);
+int menu_hostjoin(struct menu_common *c);
+int menu_settings(struct menu_common *c);
int menu_join(struct menu_common *c);
-int menu_gamecfg(struct menu_common *c);
+int menu_host(struct menu_common *c);
+int menu_gamecfg(struct menu_common *c, struct net_host *h, net_peer p);
void menu_on_pressed(void *arg);
#ifdef __cplusplus
diff --git a/src/menu/src/common.c b/src/menu/src/common.c
new file mode 100644
index 0000000..494c047
--- /dev/null
+++ b/src/menu/src/common.c
@@ -0,0 +1,11 @@
+#include <menu_private.h>
+#include <net.h>
+#include <stddef.h>
+
+const enum net_domain menu_domains[] =
+{
+ NET_DOMAIN_IPV4,
+ NET_DOMAIN_SERIAL
+};
+
+const size_t menu_domains_len = sizeof menu_domains / sizeof *menu_domains;
diff --git a/src/menu/src/gamecfg_menu.c b/src/menu/src/gamecfg_menu.c
index 12184f5..ac01718 100644
--- a/src/menu/src/gamecfg_menu.c
+++ b/src/menu/src/gamecfg_menu.c
@@ -3,27 +3,476 @@
#include <game.h>
#include <gui.h>
#include <gui/button.h>
+#include <gui/checkbox.h>
#include <gui/container.h>
+#include <gui/label.h>
+#include <gui/line_edit.h>
#include <gui/rounded_rect.h>
+#include <net.h>
+#include <packet.h>
+#include <util.h>
#include <stdbool.h>
#include <stddef.h>
+#include <string.h>
+
+enum
+{
+ CHAT_ENTRIES = 6,
+ TOTAL_MESSAGE_LEN = GAME_PLAYER_NAME_LEN
+ + sizeof ": " - 1
+ + PACKET_MAX_MESSAGE_LEN
+};
struct gamecfg_menu
{
- struct gui_container cnt, bcnt;
- struct gui_rounded_rect r;
+ char chat_in[PACKET_MAX_MESSAGE_LEN],
+ chat[CHAT_ENTRIES][TOTAL_MESSAGE_LEN];
+ char chat_label[sizeof "Chat (6)"];
+ size_t chat_i, unread_messages;
+ struct gui_container bcnt;
struct gui_button start, back;
+ struct net_host *host;
+ struct packet_ctx ctx;
+ bool update_page;
+
+ enum
+ {
+ PAGE_1,
+ PAGE_2,
+
+ N_PAGES
+ } page_i;
+
+ struct player_info
+ {
+ struct player_info_net
+ {
+ net_peer peer;
+ struct packet_input in;
+ } *net;
+
+ char name[GAME_PLAYER_NAME_LEN];
+
+ enum
+ {
+ OPEN,
+ CLOSED,
+ CONNECTING,
+ NOT_READY,
+ READY
+ } state;
+ } pl[GAME_MAX_PLAYERS];
+
+ union
+ {
+ struct gui_container root;
+
+ struct gamecfg_page1
+ {
+ struct gui_container root, cnt, ready_cnt;
+ struct gui_rounded_rect r;
+ struct gui_label ready;
+ struct gui_checkbox ready_ch;
+ struct gui_button chat_page;
+
+ struct player_info_gui
+ {
+ struct gui_container cnt;
+ struct gui_label name, state_label;
+ } pl[GAME_MAX_PLAYERS];
+ } page1;
+
+ struct gamecfg_page2
+ {
+ struct gui_container root, chat_cnt, bcnt;
+ struct gui_rounded_rect r;
+ struct gui_line_edit chat_le;
+ struct gui_button send, player_page;
+ struct gui_label entries[CHAT_ENTRIES];
+ } page2;
+ } u;
};
+static void update_players(struct gamecfg_menu *const m)
+{
+ if (m->page_i != PAGE_1)
+ return;
+
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ struct player_info *const pi = &m->pl[i];
+ struct player_info_gui *const g = &m->u.page1.pl[i];
+
+ static const char *const states[] =
+ {
+ [OPEN] = "Open",
+ [CLOSED] = "Closed",
+ [NOT_READY] = "Not ready",
+ [CONNECTING] = "Connecting",
+ [READY] = "Ready"
+ };
+
+ g->state_label.text = states[pi->state];
+ }
+}
+
+static void on_next_page_pressed(void *const arg)
+{
+ struct gamecfg_menu *const m = arg;
+
+ m->update_page = true;
+}
+
+static int send_ready(const net_peer p, const struct packet_ctx *const c,
+ const struct player_info *const pi)
+{
+ const struct packet_ready r =
+ {
+ .common =
+ {
+ .type = PACKET_TYPE_READY
+ },
+
+ .ready = pi->state == READY
+ };
+
+ return packet_send(c, p, (const union packet *)&r);
+}
+
+static void on_ready_pressed(const bool active, void *const arg)
+{
+ struct gamecfg_menu *const m = arg;
+ struct player_info *const self = m->pl;
+
+ self->state = active ? READY : NOT_READY;
+
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ struct player_info_net *const net = m->pl[i].net;
+
+ if (net && net->peer)
+ send_ready(net->peer, &m->ctx, self);
+ }
+}
+
+static void init_page1(struct gamecfg_menu *const m)
+{
+ struct gamecfg_page1 *const p = &m->u.page1;
+
+ {
+ struct gui_rounded_rect *const r = &p->r;
+
+ gui_rounded_rect_init(r);
+ r->adjust = true;
+ r->common.hcentered = true;
+ gui_add_child(&p->root.common, &r->common);
+ }
+
+ {
+ struct gui_container *const c = &p->cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_V;
+ c->spacing = 4;
+ gui_add_child(&p->r.common, &c->common);
+ }
+
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ struct player_info *const pi = &m->pl[i];
+ struct player_info_gui *const g = &p->pl[i];
+
+ {
+ struct gui_container *const c = &g->cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->spacing = 8;
+ gui_add_child(&p->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &g->name;
+
+ gui_label_init(l);
+ l->text = pi->name;
+ l->common.vcentered = true;
+ gui_add_child(&g->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_label *const l = &g->state_label;
+
+ gui_label_init(l);
+ l->common.vcentered = true;
+ gui_add_child(&g->cnt.common, &l->common);
+ }
+ }
+
+ {
+ struct gui_container *const c = &p->ready_cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->spacing = 4;
+ c->common.hcentered = true;
+ gui_add_child(&p->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_checkbox *const c = &p->ready_ch;
+
+ gui_checkbox_init(c);
+ c->common.vcentered = true;
+ c->on_pressed = on_ready_pressed;
+ c->arg = m;
+ gui_add_child(&p->ready_cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &p->ready;
+
+ gui_label_init(l);
+ l->font = FONT;
+ l->text = "Ready";
+ l->common.vcentered = true;
+ gui_add_child(&p->ready_cnt.common, &l->common);
+ }
+
+ {
+ struct gui_button *const b = &p->chat_page;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.w = 140;
+ b->common.hcentered = true;
+ b->on_pressed = on_next_page_pressed;
+ b->arg = m;
+ b->u.type1.label.text = "Chat";
+ gui_add_child(&m->u.root.common, &b->common);
+ }
+}
+
+static int broadcast(struct gamecfg_menu *const m, const union packet *const p)
+{
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ struct player_info_net *const net = m->pl[i].net;
+
+ if (net)
+ {
+ const net_peer peer = net->peer;
+
+ if (peer && packet_send(&m->ctx, peer, p))
+ {
+ fprintf(stderr, "%s: net_write failed\n", __func__);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void append_msg(struct gamecfg_menu *const m,
+ const char *const player, const char *const msg)
+{
+ static const size_t sz = sizeof m->chat / sizeof *m->chat;
+ char *dst;
+
+ if (m->chat_i < sz)
+ dst = m->chat[m->chat_i++];
+ else
+ {
+ for (size_t i = 0; i < sz - 1; i++)
+ strcpy(m->chat[i], m->chat[i + 1]);
+
+ dst = m->chat[sz - 1];
+ *dst = '\0';
+ }
+
+ strcat(dst, player);
+ strcat(dst, ": ");
+ strcat(dst, msg);
+}
+
+static void on_send_pressed(void *const arg)
+{
+ struct gamecfg_menu *const m = arg;
+ struct gui_line_edit *const le = &m->u.page2.chat_le;
+ char *const src = le->text;
+
+ if (!*src)
+ return;
+
+ const struct packet_chat c =
+ {
+ .common =
+ {
+ .type = PACKET_TYPE_CHAT
+ },
+
+ .msg = src
+ };
+
+ if (broadcast(m, (const union packet *)&c))
+ return;
+
+ append_msg(m, m->pl->name, src);
+ *src = '\0';
+ le->i = 0;
+}
+
+static void init_page2(struct gamecfg_menu *const m)
+{
+ struct gamecfg_page2 *const p = &m->u.page2;
+
+ {
+ struct gui_rounded_rect *const r = &p->r;
+
+ gui_rounded_rect_init(r);
+ r->adjust = true;
+ r->common.hcentered = true;
+ gui_add_child(&m->u.root.common, &r->common);
+ }
+
+ {
+ struct gui_container *const c = &p->chat_cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_V;
+ c->spacing = 2;
+ gui_add_child(&p->r.common, &c->common);
+ }
+
+ for (size_t i = 0; i < sizeof p->entries / sizeof *p->entries; i++)
+ {
+ struct gui_label *const l = &p->entries[i];
+
+ gui_label_init(l);
+ l->text = m->chat[i];
+ l->font = FONT;
+ gui_add_child(&p->chat_cnt.common, &l->common);
+ }
+
+ {
+ struct gui_line_edit *const le = &p->chat_le;
+
+ gui_line_edit_init(le, m->chat_in, sizeof m->chat_in);
+ le->common.hcentered = true;
+ le->w = 240;
+ gui_add_child(&m->u.root.common, &le->common);
+ }
+
+ {
+ struct gui_container *const c = &p->bcnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->spacing = 2;
+ gui_add_child(&m->u.root.common, &c->common);
+ }
+
+ {
+ struct gui_button *const b = &p->send;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->common.vcentered = true;
+ b->u.type1.w = 100;
+ b->u.type1.label.text = "Send";
+ b->on_pressed = on_send_pressed;
+ b->arg = m;
+ gui_add_child(&p->bcnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &p->player_page;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->common.vcentered = true;
+ b->u.type1.w = 150;
+ b->u.type1.label.text = "Player info";
+ b->on_pressed = on_next_page_pressed;
+ b->arg = m;
+ gui_add_child(&p->bcnt.common, &b->common);
+ }
+
+ m->unread_messages = 0;
+}
+
+static void init_page_gui(struct gamecfg_menu *const m)
+{
+ {
+ struct gui_container *const c = &m->u.root;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->common.vcentered = true;
+ c->mode = GUI_CONTAINER_MODE_V;
+ c->spacing = 4;
+ }
+
+ static void (*const init[])(struct gamecfg_menu *) =
+ {
+ [PAGE_1] = init_page1,
+ [PAGE_2] = init_page2
+ };
+
+ init[m->page_i](m);
+}
+
+static void update_send(struct menu_common *const c,
+ struct gamecfg_menu *const m)
+{
+ if (m->page_i != PAGE_2)
+ return;
+
+ switch (c->p.common.type)
+ {
+ case PERIPHERAL_TYPE_KEYBOARD_MOUSE:
+ /* Fall through. */
+ case PERIPHERAL_TYPE_TOUCH:
+ {
+ struct keyboard *const k = &c->p.kbm.keyboard;
+
+ if (m->u.page2.chat_le.focus
+ && keyboard_justreleased(k,
+ &KEYBOARD_COMBO(KEYBOARD_KEY_RETURN)))
+ on_send_pressed(m);
+ }
+ break;
+
+ case PERIPHERAL_TYPE_PAD:
+ break;
+ }
+}
+
+static void update_page(struct gamecfg_menu *const m)
+{
+ if (m->update_page)
+ {
+ if (++m->page_i >= N_PAGES)
+ m->page_i = 0;
+
+ init_page_gui(m);
+ m->update_page = false;
+ }
+}
+
static int update(struct menu_common *const c, void *const arg)
{
struct gamecfg_menu *const m = arg;
m->bcnt.common.y = screen_h - 40;
- m->r.w = screen_w / 2;
- m->r.h = screen_h / 2;
- if (gui_update(&m->cnt.common, &c->p, &c->cam, &c->in)
+ if (m->host)
+ net_update(m->host);
+
+ update_players(m);
+ update_send(c, m);
+ update_page(m);
+
+ if (gui_update(&m->u.root.common, &c->p, &c->cam, &c->in)
|| gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
return -1;
@@ -34,54 +483,264 @@ static int render(const struct menu_common *const c, void *const arg)
{
const struct gamecfg_menu *const m = arg;
- if (gui_render(&m->cnt.common)
+ if (gui_render(&m->u.root.common)
|| gui_render(&m->bcnt.common))
return -1;
return 0;
}
-int menu_gamecfg(struct menu_common *const c)
+static int add_peer(struct gamecfg_menu *const m, const net_peer p)
+{
+ bool found = false;
+
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ struct player_info *const pi = &m->pl[i];
+ struct player_info_net *const net = pi->net;
+
+ if (net && !net->peer)
+ {
+ net->peer = p;
+ pi->state = CONNECTING;
+ strcpy(pi->name, "<new player>");
+ found = true;
+ break;
+ }
+ }
+
+ return !found;
+}
+
+static int send_name(const net_peer p, const struct packet_ctx *const c,
+ const struct player_info *const pi)
+{
+ const struct packet_name n =
+ {
+ .common =
+ {
+ .type = PACKET_TYPE_NAME
+ },
+
+ .name = pi->name
+ };
+
+ return packet_send(c, p, (const union packet *)&n);
+}
+
+static int broadcast_player_info(const net_peer p,
+ const struct gamecfg_menu *const m)
+{
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ const struct player_info *const pi = &m->pl[i];
+
+ if (*pi->name)
+ {
+ const struct packet_ctx *const c = &m->ctx;
+
+ if (send_name(p, c, pi) || send_ready(p, c, pi))
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void on_connected(const net_peer p, void *const arg)
+{
+ struct gamecfg_menu *const m = arg;
+
+ if (net_role(m->host) == NET_ROLE_SERVER)
+ broadcast_player_info(p, m);
+
+ add_peer(m, p);
+}
+
+static void on_disconnected(const net_peer p, void *const arg)
{
- struct gamecfg_menu m;
+}
+
+static struct player_info *get_pl_from_peer(struct gamecfg_menu *const m,
+ const net_peer peer)
+{
+ for (size_t i = 0; i < sizeof m->pl / sizeof *m->pl; i++)
+ {
+ struct player_info *const pi = &m->pl[i];
+
+ if (pi->net && pi->net->peer == peer)
+ return pi;
+ }
+
+ return NULL;
+}
+
+static void on_received(const net_peer p, const void *const buf,
+ const size_t n, void *const arg)
+{
+ struct gamecfg_menu *const m = arg;
+ struct player_info *const pi = get_pl_from_peer(m, p);
+
+ if (pi && pi->net && packet_feed(&m->ctx, p, &pi->net->in, buf, n))
+ fprintf(stderr, "%s: packet_feed failed\n", __func__);
+}
+
+static void init_footer_gui(struct gamecfg_menu *const m,
+ bool *const start, bool *const back)
+{
+ {
+ struct gui_container *const c = &m->bcnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->spacing = 8;
+ }
+
+ if (net_role(m->host) == NET_ROLE_SERVER)
+ {
+ struct gui_button *const b = &m->start;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Start";
+ b->u.type1.w = 140;
+ b->common.vcentered = true;
+ b->on_pressed = menu_on_pressed;
+ b->arg = start;
+ b->common.vcentered = true;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &m->back;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Back";
+ b->u.type1.w = 100;
+ b->common.vcentered = true;
+ b->on_pressed = menu_on_pressed;
+ b->arg = back;
+ b->common.vcentered = true;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+}
+
+static void init_own(const struct menu_common *const c,
+ struct player_info *const pi)
+{
+ strcpy(pi->name, c->s.name);
+ pi->state = NOT_READY;
+}
+
+static void on_packet_received(const net_peer peer,
+ const union packet *const pkt, void *const arg)
+{
+ struct gamecfg_menu *const m = arg;
+
+ switch (pkt->common.type)
+ {
+ case PACKET_TYPE_CHAT:
+ {
+ struct player_info *const pi = get_pl_from_peer(m, peer);
+
+ if (pi)
+ {
+ append_msg(m, pi->name, pkt->chat.msg);
+
+ if (m->page_i == PAGE_1)
+ {
+ if (m->unread_messages < CHAT_ENTRIES)
+ m->unread_messages++;
+
+ snprintf(m->chat_label, sizeof m->chat_label,
+ "Chat (%zu)", m->unread_messages);
+ m->u.page1.chat_page.u.type1.label.text = m->chat_label;
+ }
+ }
+ }
+ break;
+
+ case PACKET_TYPE_NAME:
+ {
+ struct player_info *const pi = get_pl_from_peer(m, peer);
+
+ if (pi)
+ strcpy(pi->name, pkt->name.name);
+ }
+ break;
+
+ case PACKET_TYPE_READY:
+ {
+ struct player_info *const pi = get_pl_from_peer(m, peer);
+
+ if (pi)
+ pi->state = pkt->ready.ready ? READY : NOT_READY;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+int menu_gamecfg(struct menu_common *const c, struct net_host *const h,
+ const net_peer p)
+{
+ struct gamecfg_menu m =
+ {
+ .host = h,
+ .ctx =
+ {
+ .arg = &m,
+ .cb = on_packet_received,
+ .host = h
+ }
+ };
+
+ const struct net_event ev =
+ {
+ .connected = on_connected,
+ .disconnected = on_disconnected,
+ .received = on_received,
+ .arg = &m
+ };
+
+ struct player_info_net n[sizeof m.pl / sizeof *m.pl - 1] = {0};
bool start = false, back = false;
- gui_container_init(&m.cnt);
- m.cnt.common.hcentered = true;
- m.cnt.common.vcentered = true;
-
- gui_rounded_rect_init(&m.r);
- gui_add_child(&m.cnt.common, &m.r.common);
-
- gui_container_init(&m.bcnt);
- m.bcnt.common.hcentered = true;
- m.bcnt.mode = GUI_CONTAINER_MODE_H;
- m.bcnt.spacing = 8;
-
- gui_button_init(&m.start, GUI_BUTTON_TYPE_1);
- m.start.u.type1.label.text = "Start";
- m.start.u.type1.w = 100;
- m.start.common.vcentered = true;
- m.start.on_pressed = menu_on_pressed;
- m.start.arg = &start;
- m.back.common.vcentered = true;
- gui_add_child(&m.bcnt.common, &m.start.common);
-
- gui_button_init(&m.back, GUI_BUTTON_TYPE_1);
- m.back.u.type1.label.text = "Back";
- m.back.u.type1.w = 100;
- m.back.common.vcentered = true;
- m.back.on_pressed = menu_on_pressed;
- m.back.arg = &back;
- m.back.common.vcentered = true;
- gui_add_child(&m.bcnt.common, &m.back.common);
+ for (size_t i = 0; i < sizeof n / sizeof *n; i++)
+ m.pl[i + 1].net = &n[i];
+
+ if (net_set_event(m.host, &ev))
+ return -1;
+
+ init_own(c, m.pl);
+ init_page_gui(&m);
+ init_footer_gui(&m, &start, &back);
+
+ if (p)
+ {
+ const struct packet_ctx *const c = &m.ctx;
+
+ if (add_peer(&m, p)
+ || send_name(p, c, m.pl)
+ || send_ready(p, c, m.pl))
+ return -1;
+ }
while (!back && !c->p.common.exit && !start)
if (menu_update(c, update, render, &m))
return -1;
if (start)
- return game(NULL);
+ {
+ struct game_cfg cfg =
+ {
+ .p = &c->p
+ };
+
+ return game(&cfg);
+ }
return 0;
}
diff --git a/src/menu/src/host_menu.c b/src/menu/src/host_menu.c
new file mode 100644
index 0000000..102275e
--- /dev/null
+++ b/src/menu/src/host_menu.c
@@ -0,0 +1,159 @@
+#include <menu.h>
+#include <menu_net.h>
+#include <menu_private.h>
+#include <game.h>
+#include <gui.h>
+#include <gui/button.h>
+#include <gui/checkbox.h>
+#include <gui/container.h>
+#include <gui/label.h>
+#include <gui/line_edit.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+struct host_menu
+{
+ struct gui_container cnt, bcnt;
+ struct gui_label error;
+ struct menu_net_gui net;
+ struct gui_button host, back;
+};
+
+static void hide_elements(struct menu_net_gui *const m)
+{
+ if (m->domain == NET_DOMAIN_IPV4)
+ {
+ struct menu_ipv4_gui *const g = &m->gui.ipv4;
+
+ g->address.common.hidden = true;
+ g->address_le.common.hidden = true;
+ }
+}
+
+static int update(struct menu_common *const c, void *const arg)
+{
+ struct host_menu *const m = arg;
+
+ m->bcnt.common.y = screen_h - 40;
+ menu_net_gui_update(&m->net);
+ m->net.cnt.common.parent = &m->cnt.common;
+ hide_elements(&m->net);
+
+ if (gui_update(&m->cnt.common, &c->p, &c->cam, &c->in)
+ || gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
+ return -1;
+
+ return 0;
+}
+
+static int render(const struct menu_common *const c, void *const arg)
+{
+ const struct host_menu *const m = arg;
+
+ if (gui_render(&m->cnt.common)
+ || gui_render(&m->bcnt.common))
+ return -1;
+
+ return 0;
+}
+
+static void init_footer_gui(struct host_menu *const m,
+ bool *const back, bool *const host)
+{
+ {
+ struct gui_label *const l = &m->error;
+
+ gui_label_init(l);
+ l->font = FONT;
+ l->common.hcentered = true;
+ l->common.hidden = true;
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_container *const c = &m->bcnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->mode = GUI_CONTAINER_MODE_H;
+ }
+
+ {
+ struct gui_button *const b = &m->host;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Host";
+ b->u.type1.w = 140;
+ b->common.vcentered = true;
+ b->on_pressed = menu_on_pressed;
+ b->arg = host;
+ b->common.vcentered = true;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &m->back;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Back";
+ b->u.type1.w = 100;
+ b->common.vcentered = true;
+ b->on_pressed = menu_on_pressed;
+ b->arg = back;
+ b->common.vcentered = true;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+}
+
+static struct net_host *init_server(const struct host_menu *const m)
+{
+ union net_server s;
+
+ if (menu_net_server(&m->net, &s))
+ return NULL;
+
+ return net_server(&s);
+}
+
+int menu_host(struct menu_common *const c)
+{
+ int ret = -1;
+ bool back = false;
+
+ do
+ {
+ struct host_menu m = {0};
+ struct net_host *h = NULL;
+ bool host = false;
+
+ gui_container_init(&m.cnt);
+ m.cnt.common.hcentered = true;
+ m.cnt.common.vcentered = true;
+ m.cnt.mode = GUI_CONTAINER_MODE_V;
+ m.cnt.spacing = 2;
+
+ menu_net_gui_init(&m.net);
+ m.net.cnt.common.hcentered = true;
+ gui_add_child(&m.cnt.common, &m.net.cnt.common);
+
+ init_footer_gui(&m, &back, &host);
+
+ while (!back && !c->p.common.exit && !host)
+ if (menu_update(c, update, render, &m))
+ goto end;
+
+ if (host && (h = init_server(&m)) && menu_gamecfg(c, h, NULL))
+ goto end;
+
+ net_close(h);
+ gui_deinit(&m.cnt.common, &c->in);
+ } while (!back && !c->p.common.exit);
+
+ ret = 0;
+
+end:
+ return ret;
+}
diff --git a/src/menu/src/hostjoin_menu.c b/src/menu/src/hostjoin_menu.c
index 9ce93b1..121efd6 100644
--- a/src/menu/src/hostjoin_menu.c
+++ b/src/menu/src/hostjoin_menu.c
@@ -1,15 +1,16 @@
#include <menu.h>
#include <menu_private.h>
-#include <game.h>
#include <gui.h>
#include <gui/button.h>
#include <gui/container.h>
+#include <gui/checkbox.h>
+#include <gui/label.h>
#include <stdbool.h>
struct menu_hostjoin
{
struct gui_container cnt;
- struct gui_button host, join, back;
+ struct gui_button local, host, join, back;
};
static int update(struct menu_common *const c, void *const arg)
@@ -32,61 +33,91 @@ static int render(const struct menu_common *const c, void *const arg)
return 0;
}
-int menu_hostjoin(struct menu_common *const c, bool *const back)
+static void init_header_gui(struct menu_hostjoin *const m, bool *const local,
+ bool *const host, bool *const join)
{
+ {
+ struct gui_container *const c = &m->cnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->common.vcentered = true;
+ c->spacing = 4;
+ c->mode = GUI_CONTAINER_MODE_V;
+ }
+
+ {
+ struct gui_button *const b = &m->local;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Local game";
+ b->common.hcentered = true;
+ b->u.type1.w = 140;
+ b->arg = local;
+ b->on_pressed = menu_on_pressed;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &m->host;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Host game";
+ b->common.hcentered = true;
+ b->u.type1.w = 140;
+ b->arg = host;
+ b->on_pressed = menu_on_pressed;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &m->join;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Join game";
+ b->common.hcentered = true;
+ b->u.type1.w = 140;
+ b->arg = join;
+ b->on_pressed = menu_on_pressed;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+}
+
+static void init_footer_gui(struct menu_hostjoin *const m, bool *const back)
+{
+ struct gui_button *const b = &m->back;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Back";
+ b->common.hcentered = true;
+ b->u.type1.w = 140;
+ b->arg = back;
+ b->on_pressed = menu_on_pressed;
+ gui_add_child(&m->cnt.common, &b->common);
+}
+
+int menu_hostjoin(struct menu_common *const c)
+{
+ bool back = false;
+
do
{
struct menu_hostjoin m;
- bool host = false, join = false;
-
- gui_container_init(&m.cnt);
- m.cnt.common.hcentered = true;
- m.cnt.common.vcentered = true;
- m.cnt.spacing = 4;
- m.cnt.mode = GUI_CONTAINER_MODE_V;
-
- gui_button_init(&m.host, GUI_BUTTON_TYPE_1);
- m.host.u.type1.label.text = "Host game";
- m.host.common.hcentered = true;
- m.host.u.type1.w = 140;
- m.host.arg = &host;
- m.host.on_pressed = menu_on_pressed;
- gui_add_child(&m.cnt.common, &m.host.common);
-
- gui_button_init(&m.join, GUI_BUTTON_TYPE_1);
- m.join.u.type1.label.text = "Join game";
- m.join.common.hcentered = true;
- m.join.u.type1.w = 140;
- m.join.arg = &join;
- m.join.on_pressed = menu_on_pressed;
- gui_add_child(&m.cnt.common, &m.join.common);
-
- gui_button_init(&m.back, GUI_BUTTON_TYPE_1);
- m.back.u.type1.label.text = "Back";
- m.back.common.hcentered = true;
- m.back.u.type1.w = 140;
- m.back.arg = back;
- m.back.on_pressed = menu_on_pressed;
- gui_add_child(&m.cnt.common, &m.back.common);
-
- while (!*back && !c->p.common.exit && !host && !join)
- {
+ bool local = false, host = false, join = false;
+
+ init_header_gui(&m, &local, &host, &join);
+ init_footer_gui(&m, &back);
+
+ while (!back && !c->p.common.exit && !local && !host && !join)
if (menu_update(c, update, render, &m))
return -1;
- }
- if (host)
- {
- if (menu_gamecfg(c))
- return -1;
- }
- else if (join)
- {
- if (menu_join(c))
- return -1;
- }
+ if (host && menu_host(c))
+ return -1;
+ else if (join && menu_join(c))
+ return -1;
- } while (!*back && !c->p.common.exit);
+ } while (!back && !c->p.common.exit);
return 0;
}
diff --git a/src/menu/src/join_menu.c b/src/menu/src/join_menu.c
index 4755b5c..66a3fdc 100644
--- a/src/menu/src/join_menu.c
+++ b/src/menu/src/join_menu.c
@@ -1,5 +1,6 @@
#include <menu.h>
#include <menu_private.h>
+#include <menu_net.h>
#include <gui.h>
#include <gui/button.h>
#include <gui/container.h>
@@ -12,14 +13,13 @@
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
-#include <stdlib.h>
struct join_menu
{
struct gui_container cnt, bcnt;
- struct gui_label type_label, address_device, port, connecting, speed;
- struct gui_button type_btn, back, connect, device_btn, speed_btn;
- struct gui_line_edit address_le, port_le;
+ struct menu_net_gui net;
+ struct gui_label connecting;
+ struct gui_button back, connect;
enum
{
@@ -30,120 +30,77 @@ struct join_menu
CONNECT_FAILED
} state;
- struct net_socket *socket;
- size_t domain_i, speed_i, device_i, n_devices;
- const char *const *devices;
+ struct net_host *host;
+ net_peer peer;
};
-static const enum net_domain domains[] =
-{
- NET_DOMAIN_IPV4,
- NET_DOMAIN_SERIAL
-};
-
-static const char *const speeds[] =
-{
- "19200", "38400", "57600", "115200"
-};
-
-static void on_connected(void *const arg)
+static void on_connected(const net_peer p, void *const arg)
{
struct join_menu *const m = arg;
m->state = CONNECTED;
+ m->peer = p;
}
-static void on_disconnected(void *const arg)
+static void on_disconnected(const net_peer p, void *const arg)
{
struct join_menu *const m = arg;
m->state = CONNECT_FAILED;
- m->socket = NULL;
+ m->host = NULL;
}
static int on_connect(struct join_menu *const m)
{
- const enum net_domain d = domains[m->domain_i];
+ union net_connect c;
- union net_connect c =
+ if (menu_net_connect(&m->net, &c))
{
- .common =
- {
- .domain = d,
- .ev =
- {
- .connected = on_connected,
- .disconnected = on_disconnected,
- .arg = m
- }
- }
- };
+ fprintf(stderr, "%s: menu_net_connect failed\n", __func__);
+ return -1;
+ }
- switch (d)
+ c.common.ev = (const struct net_event)
{
- case NET_DOMAIN_IPV4:
- {
- errno = 0;
-
- const unsigned long port = strtoul(m->port_le.text, NULL, 10);
-
- if (errno)
- {
- m->connecting.text = "Invalid port";
- return -1;
- }
-
- c.ipv4.addr = m->address_le.text;
- c.ipv4.port = port;
- }
- break;
-
- case NET_DOMAIN_SERIAL:
- break;
- }
+ .connected = on_connected,
+ .disconnected = on_disconnected,
+ .arg = m
+ };
- if (!(m->socket = net_connect(&c)))
+ if (!(m->host = net_connect(&c)))
{
fprintf(stderr, "%s: net_connect failed\n", __func__);
- on_disconnected(m);
+ on_disconnected(NULL, m);
return -1;
}
return 0;
}
-static int update(struct menu_common *const c, void *const arg)
+static int update_socket(struct join_menu *const m)
{
- struct join_menu *const m = arg;
-
- m->bcnt.common.y = screen_h - 40;
-
- if (gui_update(&m->cnt.common, &c->p, &c->cam, &c->in)
- || gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
- return -1;
-
- if (m->socket && net_update(m->socket))
+ if (m->host && net_update(m->host))
return -1;
switch (m->state)
{
case CONNECT:
if (on_connect(m))
- {
m->state = CONNECT_FAILED;
+ else
+ {
+ m->state = CONNECTING;
+ m->connecting.text = "Connecting...";
+ m->connecting.common.hidden = false;
break;
}
- m->state = CONNECTING;
- m->connecting.text = "Connecting...";
- m->connecting.common.hidden = false;
- break;
-
+ /* Fall through. */
case CONNECT_FAILED:
- net_close(m->socket);
+ net_close(m->host);
m->connecting.text = "Failed to connect";
m->connecting.common.hidden = false;
- m->socket = NULL;
+ m->host = NULL;
m->state = IDLE;
break;
@@ -154,98 +111,101 @@ static int update(struct menu_common *const c, void *const arg)
return 0;
}
-static int render(const struct menu_common *const c, void *const arg)
+static void hide_elements(struct menu_net_gui *const m)
{
- const struct join_menu *const m = arg;
-
- if (gui_render(&m->cnt.common)
- || gui_render(&m->bcnt.common))
- return -1;
+ if (m->domain == NET_DOMAIN_IPV4)
+ {
+ struct menu_ipv4_gui *const g = &m->gui.ipv4;
- return 0;
+ g->announce_cnt.common.hidden = true;
+ }
}
-static int update_domain(struct join_menu *const m)
+static int update(struct menu_common *const c, void *const arg)
{
- switch (domains[m->domain_i])
- {
- case NET_DOMAIN_IPV4:
- m->port.common.hidden = false;
- m->port_le.common.hidden = false;
- m->address_le.common.hidden = false;
- m->device_btn.common.hidden = true;
- m->speed.common.hidden = true;
- m->speed_btn.common.hidden = true;
- m->address_device.text = "Address:";
- break;
+ struct join_menu *const m = arg;
- case NET_DOMAIN_SERIAL:
- m->port.common.hidden = true;
- m->port_le.common.hidden = true;
- m->address_le.common.hidden = true;
- m->device_btn.common.hidden = false;
- m->speed.common.hidden = false;
- m->speed_btn.common.hidden = false;
- m->address_device.text = "Device:";
- m->speed_btn.u.type1.label.text = speeds[m->speed_i = 0];
- m->devices = net_serial_devices(&m->n_devices);
-
- if (!m->devices)
- return -1;
-
- m->device_btn.u.type1.label.text = m->devices[m->device_i = 0];
- break;
- }
+ m->bcnt.common.y = screen_h - 40;
+ menu_net_gui_update(&m->net);
+ update_socket(m);
+ hide_elements(&m->net);
+
+ if (gui_update(&m->cnt.common, &c->p, &c->cam, &c->in)
+ || gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
+ return -1;
return 0;
}
-static void on_type_pressed(void *const arg)
+static int render(const struct menu_common *const c, void *const arg)
{
- struct join_menu *const m = arg;
+ const struct join_menu *const m = arg;
- do
- if (++m->domain_i >= sizeof domains / sizeof *domains)
- m->domain_i = 0;
- while (!net_available(domains[m->domain_i]));
+ if (gui_render(&m->cnt.common)
+ || gui_render(&m->bcnt.common))
+ return -1;
- m->type_btn.u.type1.label.text = net_domain_str(domains[m->domain_i]);
- update_domain(m);
+ return 0;
}
-static void on_device_pressed(void *const arg)
+static void on_connect_pressed(void *const arg)
{
struct join_menu *const m = arg;
- if (++m->device_i >= m->n_devices)
- m->device_i = 0;
-
- m->device_btn.u.type1.label.text = m->devices[m->device_i];
+ if (m->state == IDLE)
+ m->state = CONNECT;
}
-static void on_speed_pressed(void *const arg)
+static void init_footer_gui(struct join_menu *const m, bool *const back)
{
- struct join_menu *const m = arg;
+ {
+ struct gui_label *const l = &m->connecting;
- if (++m->speed_i >= sizeof speeds / sizeof *speeds)
- m->speed_i = 0;
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->common.hidden = true;
+ gui_add_child(&m->cnt.common, &l->common);
+ }
- m->speed_btn.u.type1.label.text = speeds[m->speed_i];
-}
+ {
+ struct gui_container *const c = &m->bcnt;
-static void on_connect_pressed(void *const arg)
-{
- struct join_menu *const m = arg;
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->mode = GUI_CONTAINER_MODE_H;
+ }
- if (m->state == IDLE)
- m->state = CONNECT;
+ {
+ struct gui_button *const b = &m->connect;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Connect";
+ b->u.type1.w = 140;
+ b->common.vcentered = true;
+ b->on_pressed = on_connect_pressed;
+ b->arg = m;
+ b->common.vcentered = true;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &m->back;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Back";
+ b->u.type1.w = 100;
+ b->common.vcentered = true;
+ b->on_pressed = menu_on_pressed;
+ b->arg = back;
+ b->common.vcentered = true;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
}
int menu_join(struct menu_common *const c)
{
int ret = -1;
struct join_menu m = {0};
- bool connect = false;
bool back = false;
gui_container_init(&m.cnt);
@@ -254,107 +214,23 @@ int menu_join(struct menu_common *const c)
m.cnt.mode = GUI_CONTAINER_MODE_V;
m.cnt.spacing = 2;
- gui_label_init(&m.type_label);
- m.type_label.common.hcentered = true;
- m.type_label.text = "Type:";
- gui_add_child(&m.cnt.common, &m.type_label.common);
-
- while (!net_available(domains[m.domain_i]))
- if (++m.domain_i >= sizeof domains / sizeof *domains)
- return -1;
-
- gui_button_init(&m.type_btn, GUI_BUTTON_TYPE_1);
- m.type_btn.arg = &m;
- m.type_btn.u.type1.w = 140;
- m.type_btn.common.hcentered = true;
- m.type_btn.on_pressed = on_type_pressed;
- m.type_btn.u.type1.label.text = net_domain_str(domains[m.domain_i]);
- gui_add_child(&m.cnt.common, &m.type_btn.common);
-
- gui_label_init(&m.address_device);
- m.address_device.common.hcentered = true;
- gui_add_child(&m.cnt.common, &m.address_device.common);
-
- gui_button_init(&m.device_btn, GUI_BUTTON_TYPE_1);
- m.device_btn.common.hcentered = true;
- m.device_btn.on_pressed = on_device_pressed;
- m.device_btn.arg = &m;
- m.device_btn.u.type1.w = 140;
- gui_add_child(&m.cnt.common, &m.device_btn.common);
-
- char address[sizeof "255.255.255.255"];
- gui_line_edit_init(&m.address_le, address, sizeof address);
- m.address_le.w = 140;
- m.address_le.common.hcentered = true;
- gui_add_child(&m.cnt.common, &m.address_le.common);
-
- gui_label_init(&m.port);
- m.port.common.hcentered = true;
- m.port.text = "Port:";
- gui_add_child(&m.cnt.common, &m.port.common);
-
- char port[sizeof "65535"];
- gui_line_edit_init(&m.port_le, port, sizeof port);
- m.port_le.w = 80;
- m.port_le.common.hcentered = true;
- gui_add_child(&m.cnt.common, &m.port_le.common);
-
- gui_label_init(&m.speed);
- m.speed.font = FONT;
- m.speed.text = "Baud rate:";
- m.speed.common.hcentered = true;
- gui_add_child(&m.cnt.common, &m.speed.common);
-
- gui_button_init(&m.speed_btn, GUI_BUTTON_TYPE_1);
- m.speed_btn.arg = &m;
- m.speed_btn.on_pressed = on_speed_pressed;
- m.speed_btn.u.type1.w = 140;
- gui_add_child(&m.cnt.common, &m.speed_btn.common);
-
- gui_label_init(&m.connecting);
- m.connecting.common.hcentered = true;
- m.connecting.common.hidden = true;
- gui_add_child(&m.cnt.common, &m.connecting.common);
-
- gui_container_init(&m.bcnt);
- m.bcnt.common.hcentered = true;
- m.bcnt.mode = GUI_CONTAINER_MODE_H;
-
- gui_button_init(&m.connect, GUI_BUTTON_TYPE_1);
- m.connect.u.type1.label.text = "Connect";
- m.connect.u.type1.w = 140;
- m.connect.common.vcentered = true;
- m.connect.on_pressed = on_connect_pressed;
- m.connect.arg = &m;
- m.back.common.vcentered = true;
- gui_add_child(&m.bcnt.common, &m.connect.common);
-
- gui_button_init(&m.back, GUI_BUTTON_TYPE_1);
- m.back.u.type1.label.text = "Back";
- m.back.u.type1.w = 100;
- m.back.common.vcentered = true;
- m.back.on_pressed = menu_on_pressed;
- m.back.arg = &back;
- m.back.common.vcentered = true;
- gui_add_child(&m.bcnt.common, &m.back.common);
-
- if (update_domain(&m))
+ if (menu_net_gui_init(&m.net))
return -1;
- while (!back && !c->p.common.exit && !connect)
- {
+ gui_add_child(&m.cnt.common, &m.net.cnt.common);
+ init_footer_gui(&m, &back);
+
+ while (!back && !c->p.common.exit && m.state != CONNECTED)
if (menu_update(c, update, render, &m))
goto end;
- }
- if (connect)
- {
-
- }
+ if (m.state == CONNECTED && menu_gamecfg(c, m.host, m.peer))
+ goto end;
ret = 0;
end:
- gui_deinit(&m.cnt.common, &c->in);
+ net_close(m.host);
+ gui_deinit(&m.net.cnt.common, &c->in);
return ret;
}
diff --git a/src/menu/src/main_menu.c b/src/menu/src/main_menu.c
index 0c9ddb0..49e0fd8 100644
--- a/src/menu/src/main_menu.c
+++ b/src/menu/src/main_menu.c
@@ -9,8 +9,8 @@
struct main_menu
{
- bool start, exit;
- struct gui_button play, exit_btn;
+ bool start, settings, exit;
+ struct gui_button play, settings_btn, exit_btn;
struct gui_container cnt;
};
@@ -36,14 +36,10 @@ static int render(const struct menu_common *const c, void *const arg)
int menu_main(struct menu_common *const c)
{
- bool back;
-
do
{
struct main_menu m = {0};
- back = false;
-
gui_container_init(&m.cnt);
m.cnt.mode = GUI_CONTAINER_MODE_V;
m.cnt.common.hcentered = true;
@@ -58,6 +54,14 @@ int menu_main(struct menu_common *const c)
m.play.u.type1.label.text = "Play";
gui_add_child(&m.cnt.common, &m.play.common);
+ gui_button_init(&m.settings_btn, GUI_BUTTON_TYPE_1);
+ m.settings_btn.on_pressed = menu_on_pressed;
+ m.settings_btn.arg = &m.settings;
+ m.settings_btn.u.type1.w = 140;
+ m.settings_btn.common.hcentered = true;
+ m.settings_btn.u.type1.label.text = "Settings";
+ gui_add_child(&m.cnt.common, &m.settings_btn.common);
+
if (system_can_exit())
{
gui_button_init(&m.exit_btn, GUI_BUTTON_TYPE_1);
@@ -69,7 +73,7 @@ int menu_main(struct menu_common *const c)
gui_add_child(&m.cnt.common, &m.exit_btn.common);
}
- while (!m.start)
+ while (!m.start && !m.settings && !c->p.common.exit)
{
if (menu_update(c, update, render, &m))
return -1;
@@ -78,10 +82,12 @@ int menu_main(struct menu_common *const c)
return 0;
}
- if (menu_hostjoin(c, &back))
+ if (m.start && menu_hostjoin(c))
+ return -1;
+ else if (m.settings && menu_settings(c))
return -1;
- } while (back && !c->p.common.exit);
+ } while (!c->p.common.exit);
return 0;
}
diff --git a/src/menu/src/menu.c b/src/menu/src/menu.c
index ba90708..eb99619 100644
--- a/src/menu/src/menu.c
+++ b/src/menu/src/menu.c
@@ -5,6 +5,7 @@
#include <gfx.h>
#include <input.h>
#include <peripheral.h>
+#include <settings.h>
#include <system.h>
#include <stdbool.h>
@@ -62,18 +63,17 @@ int menu_update(struct menu_common *const c,
int menu(void)
{
- const struct peripheral_cfg cfg =
- {
- .type = PERIPHERAL_TYPE_KEYBOARD_MOUSE
- };
-
+ struct peripheral_cfg cfg;
struct menu_common c = {0};
+ peripheral_get_default(&cfg);
+
if (game_resinit())
return -1;
cursor_init(&c.cam.cursor);
peripheral_init(&cfg, &c.p);
+ settings_load("settings.ini", &c.s);
return menu_main(&c);
}
diff --git a/src/menu/src/menu_net.c b/src/menu/src/menu_net.c
new file mode 100644
index 0000000..5b8eedc
--- /dev/null
+++ b/src/menu/src/menu_net.c
@@ -0,0 +1,447 @@
+#include <menu.h>
+#include <menu_net.h>
+#include <game.h>
+#include <gui.h>
+#include <gui/container.h>
+#include <gui/label.h>
+#include <gui/button.h>
+#include <gui/line_edit.h>
+#include <gui/checkbox.h>
+#include <net.h>
+#include <net/serial.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const enum net_domain domains[] =
+{
+ NET_DOMAIN_IPV4,
+ NET_DOMAIN_SERIAL
+};
+
+static void on_relay_pressed(const bool active, void *const arg)
+{
+ struct menu_ipv4_gui *const m = arg;
+
+ m->next_page_btn.common.hidden = !active;
+}
+
+static void init_ipv4_gui(struct menu_ipv4_gui *const m,
+ char *const addr, const size_t addrlen,
+ char *const port, const size_t portlen)
+{
+ {
+ struct gui_container *const c = &m->cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_V;
+ c->spacing = 2;
+ }
+
+ {
+ struct gui_label *const l = &m->address;
+
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->text = "Address:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_line_edit *const le = &m->address_le;
+
+ gui_line_edit_init(le, addr, addrlen);
+ le->w = 280;
+ le->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &le->common);
+ }
+
+ {
+ struct gui_label *const l = &m->port;
+
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->text = "Port:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_line_edit *const le = &m->port_le;
+
+ gui_line_edit_init(le, port, portlen);
+ le->w = 80;
+ le->common.hcentered = true;
+ le->filters = GUI_LINE_EDIT_FILTER_NUM;
+ gui_add_child(&m->cnt.common, &le->common);
+ }
+
+ {
+ struct gui_container *const c = &m->relay_cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->spacing = 4;
+ c->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_checkbox *const c = &m->relay_ch;
+
+ gui_checkbox_init(c);
+ c->common.vcentered = true;
+ c->on_pressed = on_relay_pressed;
+ c->arg = m;
+ gui_add_child(&m->relay_cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &m->relay;
+
+ gui_label_init(l);
+ l->common.vcentered = true;
+ l->font = FONT;
+ l->text = "Relay to serial";
+ gui_add_child(&m->relay_cnt.common, &l->common);
+ }
+
+ {
+ struct gui_button *const b = &m->next_page_btn;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->common.vcentered = true;
+ b->u.type1.label.text = "Next page";
+ b->u.type1.w = 140;
+ b->common.hidden = true;
+ gui_add_child(&m->relay_cnt.common, &b->common);
+ }
+
+ {
+ struct gui_container *const c = &m->announce_cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->spacing = 4;
+ c->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_checkbox *const c = &m->announce_ch;
+
+ gui_checkbox_init(c);
+ c->common.vcentered = true;
+ c->arg = m;
+ gui_add_child(&m->announce_cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &m->announce;
+
+ gui_label_init(l);
+ l->common.vcentered = true;
+ l->font = FONT;
+ l->text = "Announce server";
+ gui_add_child(&m->announce_cnt.common, &l->common);
+ }
+}
+
+static const char *const speeds[] =
+{
+ "19200", "38400", "57600", "115200"
+};
+
+static void on_speed_pressed(void *const arg)
+{
+ struct menu_serial_gui *const m = arg;
+
+ if (++m->speed_i >= sizeof speeds / sizeof *speeds)
+ m->speed_i = 0;
+
+ m->speed_btn.u.type1.label.text = speeds[m->speed_i];
+}
+
+static void init_serial_gui(struct menu_serial_gui *const m,
+ char *const buf, const size_t n)
+{
+ m->speed_i = sizeof speeds / sizeof *speeds - 1;
+
+ {
+ struct gui_container *const c = &m->cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_V;
+ c->spacing = 2;
+ }
+
+ {
+ struct gui_label *const l = &m->device;
+
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->text = "Device:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_line_edit *const le = &m->device_le;
+
+ gui_line_edit_init(le, buf, n);
+ le->w = 280;
+ le->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &le->common);
+ }
+
+ {
+ struct gui_label *const l = &m->speed;
+
+ gui_label_init(l);
+ l->font = FONT;
+ l->text = "Baud rate:";
+ l->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_button *const b = &m->speed_btn;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->arg = m;
+ b->on_pressed = on_speed_pressed;
+ b->u.type1.w = 140;
+ b->u.type1.label.text = speeds[m->speed_i];
+ b->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+
+ {
+ struct gui_container *const c = &m->hw_ctrl_cnt;
+
+ gui_container_init(c);
+ c->spacing = 8;
+ c->mode = GUI_CONTAINER_MODE_H;
+ c->common.hcentered = true;
+ gui_add_child(&m->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &m->hw_ctrl;
+
+ gui_label_init(l);
+ l->text = "HW control";
+ l->font = FONT;
+ l->common.vcentered = true;
+ gui_add_child(&m->hw_ctrl_cnt.common, &l->common);
+ }
+
+ {
+ struct gui_checkbox *const c = &m->hw_ctrl_ch;
+
+ gui_checkbox_init(c);
+ c->common.vcentered = true;
+ gui_add_child(&m->hw_ctrl_cnt.common, &c->common);
+ }
+}
+
+static void on_type_pressed(void *const arg)
+{
+ struct menu_net_gui *const m = arg;
+
+ do
+ if (++m->domain >= sizeof domains / sizeof *domains)
+ m->domain = 0;
+ while (!net_available(domains[m->domain]));
+
+ m->update_domain = true;
+}
+
+static void update_domain(struct menu_net_gui *const m)
+{
+ const enum net_domain d = domains[m->domain];
+
+ switch (d)
+ {
+ case NET_DOMAIN_IPV4:
+ init_ipv4_gui(&m->gui.ipv4,
+ m->address, sizeof m->address,
+ m->port, sizeof m->port);
+ break;
+
+ case NET_DOMAIN_SERIAL:
+ {
+ struct menu_serial_gui *const s = &m->gui.serial;
+
+ init_serial_gui(s, m->device, sizeof m->device);
+
+ if (net_serial_unique())
+ {
+ s->device.common.hidden = true;
+ s->device_le.common.hidden = true;
+ }
+ else
+ s->device.common.hidden = false;
+
+ break;
+ }
+ }
+
+ m->type_btn.u.type1.label.text = net_domain_str(d);
+ m->cnt.common.hcentered = true;
+ /* Restore parent removed by init function. */
+ m->gui.cnt.common.parent = &m->cnt.common;
+}
+
+static int set_common(const struct menu_net_gui *const m,
+ const char **const addr, short *const port,
+ const char **const dev, unsigned long *const baud)
+{
+ switch (m->domain)
+ {
+ case NET_DOMAIN_IPV4:
+ {
+ const struct menu_ipv4_gui *const g = &m->gui.ipv4;
+
+ if (addr)
+ *addr = g->address_le.text;
+
+ if (port)
+ {
+ const char *const t = g->port_le.text;
+
+ if (!*t)
+ return -1;
+
+ errno = 0;
+
+ const unsigned long port_ul = strtoul(t, NULL, 10);
+
+ if (errno)
+ {
+ fprintf(stderr, "%s: strtoul(3) failed: %s\n",
+ __func__, strerror(errno));
+ return -1;
+ }
+
+ *port = port_ul;
+ }
+ }
+ break;
+
+ case NET_DOMAIN_SERIAL:
+ {
+ const struct menu_serial_gui *const g = &m->gui.serial;
+
+ if (dev)
+ *dev = g->device_le.label.text;
+
+ if (baud)
+ {
+ const char *const t = g->speed_btn.u.type1.label.text;
+
+ if (!*t)
+ return -1;
+
+ errno = 0;
+ *baud = strtoul(t, NULL, 10);
+
+ if (errno)
+ {
+ fprintf(stderr, "%s: strtoul(3) failed: %s\n",
+ __func__, strerror(errno));
+ return -1;
+ }
+ }
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int menu_net_server(const struct menu_net_gui *const m,
+ union net_server *const s)
+{
+ *s = (const union net_server)
+ {
+ .common =
+ {
+ .domain = m->domain,
+ .max_players = GAME_MAX_PLAYERS - 1
+ }
+ };
+
+ struct net_serial_cfg *const scfg = &s->serial.cfg;
+
+ return set_common(m, NULL, &s->ipv4.port, &scfg->dev, &scfg->baud);
+}
+
+int menu_net_connect(const struct menu_net_gui *const m,
+ union net_connect *const c)
+{
+ *c = (const union net_connect)
+ {
+ .common =
+ {
+ .domain = m->domain
+ }
+ };
+
+ struct net_serial_cfg *const scfg = &c->serial.cfg;
+
+ return set_common(m, &c->ipv4.addr, &c->ipv4.port, &scfg->dev, &scfg->baud);
+}
+
+void menu_net_gui_update(struct menu_net_gui *const m)
+{
+ if (m->update_domain)
+ {
+ m->update_domain = false;
+ update_domain(m);
+ }
+}
+
+int menu_net_gui_init(struct menu_net_gui *const m)
+{
+ {
+ struct gui_container *const c = &m->cnt;
+
+ gui_container_init(c);
+ c->mode = GUI_CONTAINER_MODE_V;
+ c->spacing = 1;
+ }
+
+ {
+ struct gui_label *const l = &m->type;
+
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->text = "Type:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_button *const b = &m->type_btn;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->common.hcentered = true;
+ b->u.type1.w = 140;
+ b->u.type1.label.text = net_domain_str(domains[m->domain]);
+ b->on_pressed = on_type_pressed;
+ b->arg = m;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+
+ /* Set default port. */
+ strcpy(m->port, "7822");
+
+ while (!net_available(domains[m->domain]))
+ if (++m->domain >= sizeof domains / sizeof *domains)
+ return -1;
+
+ update_domain(m);
+ gui_add_child(&m->cnt.common, &m->gui.cnt.common);
+ return 0;
+}
diff --git a/src/menu/src/settings_menu.c b/src/menu/src/settings_menu.c
index 0e782bd..72154fb 100644
--- a/src/menu/src/settings_menu.c
+++ b/src/menu/src/settings_menu.c
@@ -1,22 +1,77 @@
#include <menu_private.h>
+#include <game.h>
+#include <gfx.h>
#include <gui.h>
#include <gui/container.h>
+#include <gui/checkbox.h>
#include <gui/label.h>
#include <gui/line_edit.h>
+#include <gui/rounded_rect.h>
#include <gui/button.h>
+#include <errno.h>
#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
struct menu_settings
{
- struct gui_container cnt;
- struct gui_button back;
+ struct gui_container cnt, bcnt, fs_cnt, res_cnt;
+ struct gui_rounded_rect r;
+ struct gui_checkbox fs_ch;
+ struct gui_label fs, xres, yres, periph, pad, name;
+ struct gui_button apply, back, periph_btn, pad_btn;
+ struct gui_line_edit name_le, xres_le, yres_le;
+ int pad_i;
+ size_t n_pads;
+ enum peripheral_type type;
+};
+
+static bool keyboard_mouse_available(void)
+{
+ return keyboard_available() && mouse_available();
+}
+
+static bool pad_available(void)
+{
+ return pad_count();
+}
+
+static const struct type
+{
+ const char *str;
+ bool (*available)(void);
+} types[] =
+{
+ [PERIPHERAL_TYPE_KEYBOARD_MOUSE] =
+ {
+ .str = "Keyboard + mouse",
+ .available = keyboard_mouse_available
+ },
+
+ [PERIPHERAL_TYPE_TOUCH] =
+ {
+ .str = "Touch",
+ .available = keyboard_mouse_available
+ },
+
+ [PERIPHERAL_TYPE_PAD] =
+ {
+ .str = "Gamepad",
+ .available = pad_available
+ }
};
static int update(struct menu_common *const c, void *const arg)
{
struct menu_settings *const m = arg;
- if (gui_update(&m->cnt.common, &c->p, &c->cam, &c->in))
+ m->bcnt.common.y = screen_h - 40;
+ m->res_cnt.common.hidden = !m->fs_ch.active;
+
+ if (gui_update(&m->r.common, &c->p, &c->cam, &c->in)
+ || gui_update(&m->bcnt.common, &c->p, &c->cam, &c->in))
return -1;
return 0;
@@ -26,37 +81,332 @@ static int render(const struct menu_common *const c, void *const arg)
{
const struct menu_settings *const m = arg;
- if (gui_render(&m->cnt.common))
+ if (gui_render(&m->r.common)
+ || gui_render(&m->bcnt.common))
return -1;
return 0;
}
-int menu_settings(struct menu_common *const c, bool *const back)
+static void update_device(struct menu_settings *const m)
+{
+ const struct type *const t = &types[m->type];
+
+ m->periph_btn.u.type1.label.text = t->str;
+
+ if (m->type == PERIPHERAL_TYPE_PAD)
+ {
+ m->pad_btn.common.hidden = false;
+ m->pad.common.hidden = false;
+ m->pad_btn.u.type1.label.text = pad_name(m->pad_i = 0);
+ }
+ else
+ {
+ m->pad_btn.common.hidden = true;
+ m->pad.common.hidden = true;
+ }
+}
+
+static void on_input_pressed(void *const arg)
{
+ struct menu_settings *const m = arg;
+ enum peripheral_type i = m->type;
+
do
{
- struct menu_settings m;
+ if (++i >= sizeof types / sizeof *types)
+ i = 0;
+
+ const struct type *const t = &types[i];
+
+ if (t->available())
+ {
+ m->type = i;
+ update_device(m);
+ break;
+ }
+
+ } while (i != m->type);
+}
+
+static void init_settings_gui(struct menu_settings *const m,
+ char *const name, const size_t name_len)
+{
+ {
+ struct gui_label *const l = &m->name;
- gui_container_init(&m.cnt);
- m.cnt.common.hcentered = true;
- m.cnt.common.vcentered = true;
- m.cnt.spacing = 4;
- m.cnt.mode = GUI_CONTAINER_MODE_V;
+ gui_label_init(l);
+ l->font = FONT;
+ l->common.hcentered = true;
+ l->text = "Name:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
- gui_button_init(&m.back, GUI_BUTTON_TYPE_1);
- m.back.u.type1.label.text = "Back";
- m.back.common.hcentered = true;
- m.back.u.type1.w = 140;
- m.back.arg = back;
- m.back.on_pressed = menu_on_pressed;
- gui_add_child(&m.cnt.common, &m.back.common);
+ {
+ struct gui_line_edit *const le = &m->name_le;
- while (!*back && !c->p.common.exit)
- if (menu_update(c, update, render, &m))
- return -1;
+ gui_line_edit_init(le, name, name_len);
+ le->common.hcentered = true;
+ le->w = 140;
+ gui_add_child(&m->cnt.common, &le->common);
+ }
- } while (!*back && !c->p.common.exit);
+ {
+ struct gui_label *const l = &m->periph;
- return 0;
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->font = FONT;
+ l->text = "Input method:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_button *const b = &m->periph_btn;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->common.hcentered = true;
+ b->u.type1.w = 200;
+ b->on_pressed = on_input_pressed;
+ b->arg = m;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+
+ {
+ struct gui_label *const l = &m->pad;
+
+ gui_label_init(l);
+ l->common.hcentered = true;
+ l->font = FONT;
+ l->text = "Pad:";
+ gui_add_child(&m->cnt.common, &l->common);
+ }
+
+ {
+ struct gui_button *const b = &m->pad_btn;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->common.hcentered = true;
+ b->u.type1.w = 200;
+ gui_add_child(&m->cnt.common, &b->common);
+ }
+
+ update_device(m);
+}
+
+static void init_display_gui(struct menu_settings *const m,
+ char *const xres, const size_t xres_len, char *const yres,
+ const size_t yres_len)
+{
+ enum {WIDTH = 60};
+
+ {
+ struct gui_container *const c = &m->res_cnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->spacing = 2;
+ c->mode = GUI_CONTAINER_MODE_H;
+ gui_add_child(&m->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &m->xres;
+
+ gui_label_init(l);
+ l->font = FONT;
+ l->text = "x:";
+ l->common.vcentered = true;
+ gui_add_child(&m->res_cnt.common, &l->common);
+ }
+
+ {
+ struct gui_line_edit *const le = &m->xres_le;
+
+ gui_line_edit_init(le, xres, xres_len);
+ le->common.vcentered = true;
+ le->w = WIDTH;
+ le->filters = GUI_LINE_EDIT_FILTER_NUM;
+ gui_add_child(&m->res_cnt.common, &le->common);
+ }
+
+ {
+ struct gui_label *const l = &m->yres;
+
+ gui_label_init(l);
+ l->font = FONT;
+ l->text = "y:";
+ l->common.vcentered = true;
+ gui_add_child(&m->res_cnt.common, &l->common);
+ }
+
+ {
+ struct gui_line_edit *const le = &m->yres_le;
+
+ gui_line_edit_init(le, yres, yres_len);
+ le->common.vcentered = true;
+ le->w = WIDTH;
+ le->filters = GUI_LINE_EDIT_FILTER_NUM;
+ gui_add_child(&m->res_cnt.common, &le->common);
+ }
+}
+
+static void init_fullscreen_gui(struct menu_settings *const m)
+{
+ {
+ struct gui_container *const c = &m->fs_cnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->spacing = 8;
+ c->mode = GUI_CONTAINER_MODE_H;
+ gui_add_child(&m->cnt.common, &c->common);
+ }
+
+ {
+ struct gui_checkbox *const c = &m->fs_ch;
+
+ gui_checkbox_init(c);
+ c->common.vcentered = true;
+ c->active = gfx_fullscreen();
+ gui_add_child(&m->fs_cnt.common, &c->common);
+ }
+
+ {
+ struct gui_label *const l = &m->fs;
+
+ gui_label_init(l);
+ l->font = FONT;
+ l->text = "Fullscreen";
+ l->common.vcentered = true;
+ gui_add_child(&m->fs_cnt.common, &l->common);
+ }
+}
+
+static void init_footer_gui(struct menu_settings *const m, bool *const back,
+ bool *const apply)
+{
+ {
+ struct gui_container *const c = &m->bcnt;
+
+ gui_container_init(c);
+ c->common.hcentered = true;
+ c->mode = GUI_CONTAINER_MODE_H;
+ }
+
+ {
+ struct gui_button *const b = &m->apply;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Apply";
+ b->common.vcentered = true;
+ b->u.type1.w = 140;
+ b->on_pressed = menu_on_pressed;
+ b->arg = apply;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+
+ {
+ struct gui_button *const b = &m->back;
+
+ gui_button_init(b, GUI_BUTTON_TYPE_1);
+ b->u.type1.label.text = "Back";
+ b->common.vcentered = true;
+ b->u.type1.w = 100;
+ b->arg = back;
+ b->on_pressed = menu_on_pressed;
+ gui_add_child(&m->bcnt.common, &b->common);
+ }
+}
+
+static int apply_settings(struct menu_common *const c,
+ struct menu_settings *const m,
+ struct settings *const s)
+{
+ if (gfx_fullscreen_available()
+ && (s->fullscreen = m->fs_ch.active))
+ {
+ const unsigned long w = strtoul(m->xres_le.text, NULL, 10),
+ h = strtoul(m->yres_le.text, NULL, 10);
+
+ if (!w)
+ {
+ fprintf(stderr, "%s: unexpected X resolution %lu\n",
+ __func__, w);
+ return -1;
+ }
+ else if (!h)
+ {
+ fprintf(stderr, "%s: unexpected Y resolution %lu\n",
+ __func__, h);
+ return -1;
+ }
+
+ s->screen_w = w;
+ s->screen_h = h;
+ }
+
+ s->periph_type = m->type;
+ s->pad_i = m->pad_i;
+ strcpy(s->name, m->name_le.text);
+ return settings_apply(s, &(const struct settings_rt){.p = &c->p});
+}
+
+int menu_settings(struct menu_common *const c)
+{
+ int ret = -1;
+ struct menu_settings m = {0};
+
+ {
+ struct gui_rounded_rect *const r = &m.r;
+
+ gui_rounded_rect_init(r);
+ r->adjust = true;
+ r->common.hcentered = true;
+ r->common.vcentered = true;
+ }
+
+ {
+ struct gui_container *const c = &m.cnt;
+
+ gui_container_init(c);
+ c->spacing = 4;
+ c->common.hcentered = true;
+ c->common.vcentered = true;
+ c->mode = GUI_CONTAINER_MODE_V;
+ gui_add_child(&m.r.common, &c->common);
+ }
+
+ struct settings *const s = &c->s;
+ char name[sizeof s->name], xres[sizeof "65535"], yres[sizeof xres];
+ bool back = false;
+
+ strcpy(name, s->name);
+ m.type = c->p.common.type;
+ init_settings_gui(&m, name, sizeof name);
+
+ if (gfx_fullscreen_available())
+ {
+ init_fullscreen_gui(&m);
+ snprintf(xres, sizeof xres, "%hd", s->screen_w);
+ snprintf(yres, sizeof yres, "%hd", s->screen_h);
+ init_display_gui(&m, xres, sizeof xres, yres, sizeof yres);
+ }
+
+ bool apply = false;
+
+ init_footer_gui(&m, &back, &apply);
+
+ while (!back && !c->p.common.exit && !apply)
+ if (menu_update(c, update, render, &m))
+ goto end;
+
+ if (apply && apply_settings(c, &m, s))
+ goto end;
+
+ ret = 0;
+
+end:
+ gui_deinit(&m.cnt.common, &c->in);
+ return ret;
}