commit 6746c27699ac81ff9a0355675c4589517d2376f4
Author: hhvn <dev@hhvn.uk>
Date: Fri, 3 Jun 2022 22:25:10 +0100
Init
Diffstat:
A | .gitignore | | | 7 | +++++++ |
A | Makefile | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
A | data/DejaVuSansMono.ttf | | | 0 | |
A | data/Makefile | | | 22 | ++++++++++++++++++++++ |
A | data/worlds-parse.awk | | | 130 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | db/Makefile | | | 11 | +++++++++++ |
A | db/db.c | | | 607 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | db/db.h | | | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | db/dbtool.c | | | 95 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/data.c | | | 6 | ++++++ |
A | src/main.c | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
A | src/main.h | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/save.c | | | 17 | +++++++++++++++++ |
A | src/str.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/struct.h | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/style.h | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/system.c | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/ui.c | | | 241 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
18 files changed, 1624 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,7 @@
+*.o
+data/*.h
+data/sol
+data/worlds.tsv
+db/dbtool
+game
+testdb/
diff --git a/Makefile b/Makefile
@@ -0,0 +1,42 @@
+DATADIR = data
+DBDIR = db
+DBLIB = $(DBDIR)/db.o
+DBTOOL = $(DBDIR)/dbtool
+SRCDIR = src
+SRC = $(shell find $(SRCDIR) -name "*.c")
+OBJ = $(SRC:.c=.o)
+BIN = game
+RAYLIB = -lraylib -lGL -lm -lpthread -ldl -lrt -lX11
+LDFLAGS = $(RAYLIB) $(DBLIB)
+CFLAGS = -g3 -O0
+
+all: db data $(BIN)
+
+$(BIN): $(OBJ)
+ $(CC) $(LDFLAGS) -o $(BIN) $(OBJ)
+
+clean: db-clean
+ rm -f $(BIN) $(OBJ)
+
+db:
+ @echo $(DBDIR): make $(DBLIB)
+ @cd $(DBDIR); make `basename $(DBLIB)`
+db-clean:
+ @echo $(DBDIR): make clean
+ @cd $(DBDIR); make clean
+dbtool:
+ @echo $(DBDIR): make $(DBTOOL)
+ @cd $(DBDIR); make dbtool
+
+data:
+ @echo $(DATADIR): make
+ @cd $(DATADIR); make
+data-clean:
+ @echo $(DATADIR): make clean
+ @cd $(DATADIR); make clean
+
+# ignore generated headers
+sloccount:
+ sloccount $(SRC) $(DBDIR)
+
+.PHONY: all db db-clean dbtool data data-clean
diff --git a/data/DejaVuSansMono.ttf b/data/DejaVuSansMono.ttf
Binary files differ.
diff --git a/data/Makefile b/data/Makefile
@@ -0,0 +1,22 @@
+FONTS = $(shell find . -name "*.ttf")
+HEADERS = $(FONTS:.ttf=.h)
+
+all: $(HEADERS) sol
+
+sol: worlds.tsv
+ rm -rf sol
+ ./worlds-parse.awk < $<
+ printf "main\tsol\n" > sol/index
+
+# Thanks Jonathan
+worlds.tsv:
+ curl https://planet4589.org/space/gcat/tsv/worlds/worlds.tsv | \
+ sed 's/ *\t/\t/g' > $@
+
+.ttf.h:
+ xxd -i < $< > $@
+
+clean:
+ rm -rf $(HEADERS) sol
+
+.SUFFIXES: .h .ttf
diff --git a/data/worlds-parse.awk b/data/worlds-parse.awk
@@ -0,0 +1,130 @@
+#!/bin/awk -f
+
+BEGIN {
+ FS = "\t"
+}
+
+!/^#/ {
+ id = $1
+ idname = $2
+ name = $3
+ alt = $4
+ radius = $5
+ pradius = $6
+ mass = $7
+ maxorb = $8
+ minorb = $9
+ ecc = $10
+ inc = $11
+ node = $12
+ peri = $13
+ anomoly = $14
+ epoch = $15
+ rotdays = $16
+ orbdays = $17
+ ephemeris = $18
+ type = $19
+ parent = $20
+
+ if (type == "DB" || type == "DL") # not a body
+ next
+
+ if (name == "-")
+ name = idname
+
+ if (name == "Sun") {
+ name = "Sol"
+ alt = "Sun"
+ minorb = 0
+ maxorb = 0
+ orbdays = 0
+ }
+
+ if (type == "*") nicetype = "Star"
+ if (type == "A") nicetype = "Asteroid"
+ if (type == "AS") nicetype = "Asteroid moon"
+ if (type == "M") nicetype = "Moon"
+ if (type == "P") nicetype = "Planet"
+
+ if (type == "W") {
+ if (parent == "-")
+ nicetype = "Dwarf planet"
+ else
+ nicetype = "Moon"
+ }
+
+ if (type == "CP" || type == "C") nicetype = "Comet"
+
+ if (parent == "EMB") {
+ if (name == "Earth")
+ parent = "-"
+ else
+ parent = "Earth"
+ }
+
+ if (parent == "-")
+ parent = "Sol"
+
+ if (mass ~ /\?$/)
+ sub(/\?$/, "", mass)
+
+ if (mass ~ /M$/) {
+ sub(/M$/, "", mass)
+ massmult = 1000000
+ } else {
+ massmult = 1
+ }
+
+ if (mass ~ /e+/) {
+ mantissa = mass
+ sub(/e+.*/, "", mantissa)
+ exponent = mass
+ sub(/.*e+/, "", exponent)
+ mass = mantissa * (10 ^ exponent)
+ }
+
+ mass = mass * massmult
+
+ if (minorb ~ /M$/) {
+ sub(/M$/, "", minorb)
+ minorb = minorb * 1000000
+ }
+
+ if (maxorb ~ /M$/) {
+ sub(/M$/, "", maxorb)
+ maxorb = maxorb * 1000000
+ }
+
+ if (minorb == 0)
+ minorb = maxorb
+
+ if (orbdays ~ /yr$/) {
+ sub(/yr$/, "", orbdays)
+ orbdays = orbdays * 365.25 # good enough
+ }
+
+ if (name != "Sol" && (radius ~ /^[-?0]$/ || maxorb ~ /^[-?0]$/ || mass ~ /^[-?0]$/ || orbdays ~ /^[-?0]$/))
+ next
+
+ file = sprintf("sol/%s", name)
+ system(sprintf("mkdir -p $(dirname \"%s\")", file))
+
+ if (name != "Sol")
+ printf("parent\t%s\n", parent) > file
+ printf("type\t%s\n", nicetype) > file
+ printf("radius\t%d\n", radius) > file
+ printf("mass\t%d\n", mass) > file
+ if (rotdays !~ /^[-?0]$/)
+ printf("rotdays\t%f\n", rotdays) > file
+ printf("orbdays\t%f\n", orbdays) > file
+
+ if (nicetype == "Comet") {
+ printf("mindist\t%d\n", minorb) > file
+ printf("maxdist\t%d\n", maxorb) > file
+ printf("curdist\t%d\n", (rand() * (maxorb - minorb)) + minorb) > file
+ } else {
+ printf("dist\t%d\n", (minorb + maxorb) / 2) > file
+ }
+
+ printf("curtheta\t%d\n", rand() * 360) > file
+}
diff --git a/db/Makefile b/db/Makefile
@@ -0,0 +1,11 @@
+SRC = db.c dbtool.c
+OBJ = $(SRC:.c=.o)
+BIN = dbtool
+
+$(BIN): $(OBJ)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $(BIN) $(OBJ)
+
+clean:
+ rm -f $(OBJ) $(BIN)
+
+.PHONY: all clean
diff --git a/db/db.c b/db/db.c
@@ -0,0 +1,607 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "db.h"
+
+typedef struct dbGroup dbGroup;
+typedef struct dbPair dbPair;
+
+struct dbGroup {
+ char *dir;
+ char *name;
+ char *path;
+ dbPair *pairs;
+};
+
+struct dbPair {
+ dbPair *prev;
+ char *key;
+ char *val;
+ dbPair *next;
+};
+
+/* produced only by dirlist()
+ * A linked list is much easier to append to than a dynamically sized array.
+ * This is especially the case in a recursive function, like dirlist(),
+ * where I would probably need to use static variables to keep track
+ * of the size and how much has been used. It is preferable to not use static
+ * variables in case I ever want to use multithreading.
+ *
+ * A possible alternative would be to have a dirlist_backend() function that
+ * returns the files/dirs in a dir. The frontend would then append the files to
+ * its return list, and append the dirs to another list: it keeps calling the
+ * backend for each element in this list until none remain. */
+struct Dirlist {
+ char *path;
+ struct Dirlist *next;
+};
+
+static int track(dbGroup *group);
+static int untrack(dbGroup *group);
+static dbGroup *gettracked(char *dir, char *group);
+
+static int dirlist(struct Dirlist **list, char *dir);
+
+static dbGroup *dbinitgroup_p(char *dir, char *group);
+static size_t dblistkeys_p(char ***ret, dbGroup *group);
+static dbGroup *dbgetgroup_p(char *dir, char *group, int init);
+static dbPair *dbgetpair_p(dbGroup *group, char *key);
+static int dbset_p(dbPair *pair, char *val);
+static int dbdelgroup_p(dbGroup *group);
+static void dbdelpair_p(dbGroup *group, dbPair *pair);
+static int dbwritegroup_p(dbGroup *group);
+static void dbfreegroup_p(dbGroup *group);
+
+static dbGroup *tracked[MAXGROUPS] = {NULL};
+
+/*
+ * Tracking loaded groups
+ */
+static int
+track(dbGroup *group) {
+ int i;
+ if (!group) return -1;
+ for (i = 0; i < MAXGROUPS; i++) {
+ if (!tracked[i]) {
+ tracked[i] = group;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+untrack(dbGroup *group) {
+ int i;
+ if (!group) return -1;
+ for (i = 0; i < MAXGROUPS; i++) {
+ if (tracked[i] == group) {
+ tracked[i] = NULL;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static dbGroup *
+gettracked(char *dir, char *group) {
+ int i;
+ for (i = 0; i < MAXGROUPS; i++)
+ if (tracked[i] &&
+ strcmp(dir, tracked[i]->dir) == 0 &&
+ strcmp(group, tracked[i]->name) == 0)
+ return tracked[i];
+ return NULL;
+}
+
+/*
+ * Various struct/data/etc handling
+ */
+static int
+appendpair(dbPair **pairs, char *key, char *val) {
+ dbPair *p, *new;
+
+ if (!pairs || !key) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ new = malloc(sizeof(dbPair));
+ if (!new) return -1; /* ENOMEM */
+ new->key = strdup(key);
+ new->val = val ? strdup(val) : NULL;
+ if (!new->key || (val && !new->val)) {
+ free(new->key);
+ free(new->val);
+ free(new);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (!*pairs) {
+ *pairs = new;
+ new->prev = new->next = NULL;
+ } else {
+ for (p = *pairs; p && p->next; p = p->next);
+ p->next = new;
+ new->prev = p;
+ new->next = NULL;
+ }
+ return 0;
+}
+
+char **
+dblistdup(char **list, size_t len) {
+ char **ret;
+ int i;
+
+ if (!len || !list) {
+ errno = EINVAL;
+ return NULL;
+ }
+ ret = malloc(len * sizeof(char *));
+ if (!ret) return NULL;
+ for (i = 0; i < len; i++) {
+ *(ret + i) = *(list + i) ? strdup(*(list + i)) : NULL;
+ if (*(list + i) && !*(ret + i)) {
+ while (i--)
+ free(*(ret + i));
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+ return ret;
+}
+
+void
+dblistfree(char **list, size_t len) {
+ int i;
+
+ if (!len || !list) return;
+ for (i = 0; i < len; i++)
+ free(*(list + i));
+ free(list);
+}
+
+int
+dblisteq(char **l1, size_t s1, char **l2, size_t s2) {
+ int i;
+ /* The order of these if statements are significant. */
+ if (s1 != s2)
+ return 0;
+ if (l1 == l2) /* previous statement handles different sizes */
+ return 1;
+ if (!l1 || !l2) /* previous statement handles both being null */
+ return 0;
+ for (i = 0; i < s1; i++)
+ if (strcmp(*(l1 + i), *(l2 + i)) != 0)
+ return 0;
+ return 1;
+}
+
+static void
+freepair(dbPair *pair) {
+ if (pair) {
+ free(pair->key);
+ free(pair->val);
+ free(pair);
+ }
+}
+
+/*
+ * Init
+ */
+static dbGroup *
+dbinitgroup_p(char *dir, char *group) {
+ dbGroup *ret;
+
+ if (!dir || !group) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if ((ret = gettracked(dir, group)))
+ return ret;
+ ret = malloc(sizeof(dbGroup));
+ if (!ret) return NULL;
+ ret->path = malloc(strlen(dir) + strlen(group) + 2);
+ ret->dir = strdup(dir);
+ ret->name = strdup(group);
+ ret->pairs = NULL;
+ if (!ret->path || !ret->dir || !ret->name) {
+ free(ret->path);
+ free(ret->dir);
+ free(ret->name);
+ free(ret);
+ errno = ENOMEM;
+ return NULL;
+ }
+ sprintf(ret->path, "%s/%s", dir, group);
+ track(ret);
+ return ret;
+}
+
+static dbGroup *
+dbloadgroup_p(char *dir, char *group) {
+ char path[PATH_MAX];
+ char buf[8192];
+ char *key, *val;
+ dbGroup *ret;
+ FILE *fp;
+
+ if (!dir || !group) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if ((ret = gettracked(dir, group)))
+ return ret;
+
+ snprintf(path, sizeof(path), "%s/%s", dir, group);
+ if (!(fp = fopen(path, "r")))
+ return NULL; /* errno set by fopen */
+ if (!(ret = dbinitgroup_p(dir, group)))
+ return NULL;
+ while (fgets(buf, sizeof(buf), fp)) {
+ buf[strlen(buf) - 1] = '\0'; /* remove \n */
+ key = strtok_r(buf, "\t", &val);
+ appendpair(&ret->pairs, key, val);
+ }
+ return ret;
+}
+
+/*
+ * List
+ */
+static int
+dirlist(struct Dirlist **list, char *dir) {
+ struct dirent **dirent;
+ struct stat st;
+ struct Dirlist *p, *prev;
+ char path[PATH_MAX];
+ int n, i;
+
+ n = scandir(dir, &dirent, 0, alphasort);
+ if (n < 0) {
+ return -1; /* scandir sets errno */
+ } else {
+ for (i = 0; i < n; i++) {
+ snprintf(path, sizeof(path), "%s/%s", dir, dirent[i]->d_name);
+ if (strcmp(dirent[i]->d_name, "..") != 0 &&
+ strcmp(dirent[i]->d_name, ".") != 0 &&
+ stat(path, &st) != -1) {
+ if (S_ISDIR(st.st_mode)) {
+ dirlist(list, path);
+ } else {
+ if (!(p = malloc(sizeof(struct Dirlist))) || !(p->path = strdup(path))) {
+ free(p);
+ for (prev = NULL; p; p = p->next) {
+ free(p->path);
+ free(prev);
+ prev = p;
+ }
+ errno = ENOMEM;
+ return -1;
+ }
+ p->next = *list;
+ *list = p;
+ }
+ }
+ free(dirent[i]);
+ }
+ free(dirent);
+ }
+ return 0;
+}
+
+size_t
+dblistgroups(char ***ret, char *dir) {
+ struct Dirlist *res, *p, *prev;
+ size_t len, i;
+
+ if (!ret || !dir) {
+ errno = EINVAL;
+ if (ret) *ret = NULL;
+ return 0;
+ }
+ if (dirlist(&res, dir) == -1) {
+ *ret = NULL;
+ return 0;
+ }
+ for (p = res, len = 0; p; p = p->next)
+ len++;
+ *ret = malloc(len * sizeof(char *));
+ if (!*ret) {
+ *ret = NULL;
+ return 0; /* malloc sets errno */
+ }
+ for (p = res, prev = NULL; p; p = p->next) {
+ *((*ret) + i++) = strdup(p->path + strlen(dir) + 1);
+ free(p->path);
+ free(prev);
+ prev = p;
+ }
+ return len;
+}
+
+static size_t
+dblistkeys_p(char ***ret, dbGroup *group) {
+ size_t i = 0;
+ size_t len;
+ dbPair *p;
+
+ if (!ret || !group) {
+ errno = EINVAL;
+ if (ret) *ret = NULL;
+ return 0;
+ }
+
+ for (p = group->pairs, len = 0; p; p = p->next)
+ len++;
+
+ *ret = malloc(len * sizeof(char *));
+ if (!*ret) {
+ *ret = NULL;
+ return 0;
+ }
+ for (p = group->pairs; p && i < len; p = p->next)
+ *((*ret) + i++) = p->key ? strdup(p->key) : NULL;
+ return len;
+}
+
+size_t
+dblistkeys(char ***ret, char *dir, char *group) {
+ dbGroup *p;
+ if (!(p = dbgetgroup_p(dir, group, 0))) {
+ *ret = NULL;
+ return 0;
+ }
+ return dblistkeys_p(ret, p);
+}
+
+/*
+ * Get
+ */
+static dbGroup *
+dbgetgroup_p(char *dir, char *group, int init) {
+ dbGroup *p;
+
+ if (!dir || !group) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if ((p = gettracked(dir, group)))
+ return p;
+ if ((p = dbloadgroup_p(dir, group)))
+ return p;
+ if (init)
+ return dbinitgroup_p(dir, group);
+ errno = ENOENT;
+ return NULL;
+}
+
+static dbPair *
+dbgetpair_p(dbGroup *group, char *key) {
+ dbPair *p;
+
+ if (!group || !key) {
+ errno = EINVAL;
+ return NULL;
+ }
+ for (p = group->pairs; p; p = p->next)
+ if (strcmp(p->key, key) == 0)
+ return p;
+ errno = EINVAL;
+ return NULL;
+}
+
+char *
+dbget(char *dir, char *group, char *key) {
+ dbGroup *gp;
+ dbPair *pp;
+ if (!(gp = dbgetgroup_p(dir, group, 0)) ||
+ !(pp = dbgetpair_p(gp, key)))
+ return NULL;
+ return pp->val;
+}
+
+/*
+ * Set
+ */
+static int
+dbset_p(dbPair *pair, char *val) {
+ if (!pair) {
+ errno = EINVAL;
+ return -1;
+ }
+ free(pair->val);
+ pair->val = val ? strdup(val) : NULL;
+ if (val && !pair->val) return -1; /* ENOMEM */
+ return 0;
+}
+
+int
+dbset(char *dir, char *group, char *key, char *val) {
+ dbGroup *gp;
+ dbPair *pp;
+ if (!(gp = dbgetgroup_p(dir, group, 1)))
+ return -1;
+ if (!(pp = dbgetpair_p(gp, key)))
+ return appendpair(&gp->pairs, key, val);
+ return dbset_p(pp, val);
+}
+
+/*
+ * Del
+ */
+static int
+dbdelgroup_p(dbGroup *group) {
+ int ret = 0, serrno;
+ ret = unlink(group->path);
+ serrno = errno;
+ dbfreegroup_p(group);
+ if (ret == -1)
+ errno = serrno;
+ return ret;
+}
+
+int
+dbdelgroup(char *dir, char *group) {
+ dbGroup *p;
+ if (!(p = dbgetgroup_p(dir, group, 0)))
+ return -1;
+ return dbdelgroup_p(p);
+}
+
+static void
+dbdelpair_p(dbGroup *group, dbPair *pair) {
+ if (!pair) return;
+ if (pair == group->pairs)
+ group->pairs = pair->next;
+ if (pair->next)
+ pair->next->prev = pair->prev;
+ if (pair->prev)
+ pair->prev->next = pair->next;
+ freepair(pair);
+}
+
+int
+dbdelpair(char *dir, char *group, char *key) {
+ dbGroup *gp;
+ dbPair *pp;
+ if (!(gp = dbgetgroup_p(dir, group, 0)) ||
+ !(pp = dbgetpair_p(gp, key)))
+ return -1;
+ dbdelpair_p(gp, pp);
+ return 0;
+}
+
+/*
+ * Write
+ */
+static int
+mkdirp(char *path, mode_t mode) {
+ struct stat st;
+ int serrno;
+ char *p;
+
+ path = strdup(path);
+ if (!path)
+ return -1; /* use strdup's errno */
+
+ p = path;
+ while (p) {
+ p = strchr(p + 1, '/');
+ if (p) {
+ while (*(p + 1) == '/')
+ p++;
+ *p = '\0';
+ }
+ if (mkdir(path, mode) == -1) {
+ serrno = errno;
+ if (stat(path, &st) == -1) {
+ errno = serrno;
+ return -1;
+ } else if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ return -1;
+ }
+ }
+ if (p) *p = '/';
+ }
+ return 0;
+}
+
+static int
+dbwritegroup_p(dbGroup *group) {
+ FILE *fp;
+ dbPair *p;
+
+ if (!group || !group->path) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (mkdirp(group->dir, 0755) == -1)
+ return -1; /* errno set by mkdirp */
+ if (!(fp = fopen(group->path, "w")))
+ return -1; /* errno set by fopen */
+
+ for (p = group->pairs; p; p = p->next)
+ fprintf(fp, "%s\t%s\n", p->key, p->val);
+ return 0;
+}
+
+int
+dbwritegroup(char *dir, char *group) {
+ dbGroup *p;
+ if (!(p = dbgetgroup_p(dir, group, 0)))
+ return -1;
+ return dbwritegroup_p(p);
+}
+
+int
+dbwrite(char *dir) {
+ int ret = 0, i;
+
+ if (!dir) {
+ errno = EINVAL;
+ return -1;
+ }
+ for (i = 0; i < MAXGROUPS; i++)
+ if (tracked[i] && strcmp(dir, tracked[i]->dir) == 0)
+ if (dbwritegroup_p(tracked[i]) == -1)
+ ret = -1;
+ return ret;
+}
+
+/*
+ * Free
+ */
+
+void
+dbfree(char *dir) {
+ int i;
+
+ for (i = 0; i < MAXGROUPS; i++)
+ if (tracked[i] && strcmp(dir, tracked[i]->dir) == 0)
+ dbfreegroup_p(tracked[i]);
+}
+
+static void
+dbfreegroup_p(dbGroup *group) {
+ dbPair *p, *prev;
+ if (group) {
+ untrack(group);
+ prev = group->pairs;
+ if (prev)
+ p = prev->next;
+ while (prev) {
+ freepair(prev);
+ prev = p;
+ if (p) p = p->next;
+ }
+ free(group->dir);
+ free(group->name);
+ free(group->path);
+ free(group);
+ }
+}
+
+void
+dbfreegroup(char *dir, char *group) {
+ dbGroup *p;
+ if ((p = dbgetgroup_p(dir, group, 0)))
+ dbfreegroup_p(p);
+}
+
+void
+dbcleanup(void) {
+ int i;
+
+ for (i = 0; i < MAXGROUPS; i++)
+ if (tracked[i])
+ dbfreegroup_p(tracked[i]);
+}
diff --git a/db/db.h b/db/db.h
@@ -0,0 +1,119 @@
+#include <stddef.h>
+
+#define MAXGROUPS 256
+#define TODO
+
+/* -----
+ * List functions
+ */
+
+/* Set *ret to a list of groups, and return the length of the list.
+ * On failure, set *ret to NULL, and return 0;
+ * The data returned by this function must be free'd using dblistfree()
+ * Errors may be those of scandir(3) or:
+ * EINVAL ret, dir or group are NULL
+ * ENOMEM allocation function failed
+ */
+size_t dblistgroups(char ***ret, char *dir);
+
+/* Set *ret to a list of keys, and return the length of the list.
+ * On failure, set *ret to NULL, and return 0;
+ * The data returned by this function must be free'd using dblistfree()
+ * Errors:
+ * EINVAL ret, dir or group are NULL
+ * ENOENT no such group
+ * ENOMEM allocation function failed
+ */
+size_t dblistkeys(char ***ret, char *dir, char *group);
+
+/* Returns a duplicated list, or NULL for failure.
+ * The data returned by this function must be free'd using dblistfree()
+ * Errors:
+ * EINVAL list is NULL, or len is 0
+ * ENOMEM allocation function failed
+ */
+char **dblistdup(char **list, size_t len);
+
+/* Free memory allocated by:
+ * dblistgroups(), dblistkeys(), dblistdup() */
+void dblistfree(char **list, size_t len);
+
+/* Returns 1 if lists are equal, and 0 if not. */
+int dblisteq(char **l1, size_t s1, char **l2, size_t s2);
+
+/* -----
+ * Manipulating values
+ */
+
+/* Returns a value, or NULL for failure.
+ * Errors:
+ * EINVAL dir, group, or key are NULL
+ * ENOENT no such group
+ */
+char *dbget(char *dir, char *group, char *key);
+
+/* Set a value.
+ * Returns 0 for success, or -1 for failure.
+ * Errors:
+ * EINVAL dir, group, or key is NULL
+ * ENOENT no such group
+ * ENOMEM allocation function failed
+ */
+int dbset(char *dir, char *group, char *key, char *val);
+
+/* -----
+ * Deleting groups/pairs
+ */
+
+/* Free a group and delete it from storage.
+ * Returns 0 for success or -1 for failure.
+ * Errors are that of unlink(3) or:
+ * EINVAL dir or group are NULL
+ * ENOENT no such group
+ */
+int dbdelgroup(char *dir, char *group);
+
+/* Free a pair and remove it from the group.
+ * dbwrite() or dbwritegroup() must still be called on
+ * the parent group to remove this pair from storage.
+ * Returns 0 for success or -1 for failure.
+ * Errors:
+ * EINVAL dir, group or key is NULL
+ * ENOENT no such group/key
+ */
+int dbdelpair(char *dir, char *group, char *key);
+
+/* -----
+ * Writing db data to storage
+ */
+
+/* Write a group to file.
+ * Returns 0 for success or -1 for failure.
+ * Errors are that of mkdir(3), fopen(3), or:
+ * EINVAL dir or group are NULL
+ * ENOENT no such group
+ */
+int dbwritegroup(char *dir, char *group);
+
+/* Writes all groups in a directory to file.
+ * Returns 0 for success or -1 for failure.
+ * Every write is attempted even if one fails.
+ * Errors are that of mkdir(3), fopen(3), or:
+ * EINVAL dir is NULL
+ */
+int dbwrite(char *dir);
+
+/* -----
+ * Cleanup
+ */
+
+/* Discards and frees the content of groups in a directory. */
+void dbfree(char *dir);
+
+/* Discards and frees the content of a group. */
+void dbfreegroup(char *dir, char *group);
+
+/* Discards and frees everything used by db.c.
+ * Data from dblistgroups(), dblistkeys() and dblistdup() are excluded:
+ * dblistfree() must be used on the data returned by these functions. */
+void dbcleanup(void);
diff --git a/db/dbtool.c b/db/dbtool.c
@@ -0,0 +1,95 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libgen.h>
+#include "db.h"
+
+void
+fail(char *func) {
+ perror(func);
+ dbcleanup();
+ exit(EXIT_FAILURE);
+}
+
+void
+usage(char *name) {
+ char *base;
+ base = basename(name);
+ fprintf(stderr, "usage: %s <dir>\n", base);
+ fprintf(stderr, " %s <dir> dump [group]\n", base);
+ fprintf(stderr, " %s <dir> get <group> <key>\n", base);
+ fprintf(stderr, " %s <dir> set <group> <key> [val]\n", base);
+ fprintf(stderr, " %s <dir> del <group> [key]\n", base);
+}
+
+void
+listpairs(char *dir, char *group) {
+ char **list;
+ size_t len, i;
+
+ len = dblistkeys(&list, dir, group);
+ if (!list)
+ fail("dblistkeys()");
+ for (i = 0; i < len; i++)
+ printf("%s=%s\n", *(list + i), dbget(dir, group, *(list + i)));
+ dblistfree(list, len);
+}
+
+int
+main(int argc, char *argv[]) {
+ char *p;
+ char **list;
+ size_t len, i;
+
+ if (argc == 2) {
+ len = dblistgroups(&list, argv[1]);
+ if (!list)
+ fail("dblistgroups()");
+ for (i = 0; i < len; i++)
+ printf("%s\n", *(list + i));
+ } else if (argc == 3 && strcmp(argv[2], "dump") == 0) {
+ len = dblistgroups(&list, argv[1]);
+ if (!list)
+ fail("dblistgroups()");
+ for (i = 0; i < len; i++) {
+ printf("%s:\n", *(list + i));
+ listpairs(argv[1], *(list + i));
+ if (i != len - 1)
+ printf("\n");
+ }
+ } else if (argc == 4 && strcmp(argv[2], "dump") == 0) {
+ listpairs(argv[1], argv[3]);
+ } else if (argc == 4 && strcmp(argv[2], "get") == 0) {
+ len = dblistkeys(&list, argv[1], argv[3]);
+ if (!list)
+ fail("dblistkeys()");
+ for (i = 0; i < len; i++)
+ printf("%s\n", *(list + i));
+ dblistfree(list, len);
+ } else if (argc == 5 && strcmp(argv[2], "get") == 0) {
+ if (!(p = dbget(argv[1], argv[3], argv[4])))
+ fail("dbget()");
+ printf("%s\n", p);
+ } else if (argc == 5 && strcmp(argv[2], "set") == 0) {
+ if (dbset(argv[1], argv[3], argv[4], NULL) == -1)
+ fail("dbset()");
+ dbwrite(argv[1]);
+ } else if (argc == 6 && strcmp(argv[2], "set") == 0) {
+ if (dbset(argv[1], argv[3], argv[4], argv[5]) == -1)
+ fail("dbset()");
+ dbwrite(argv[1]);
+ } else if (argc == 4 && strcmp(argv[2], "del") == 0) {
+ if (dbdelgroup(argv[1], argv[3]) == -1)
+ fail("dbdelgroup()");
+ dbwrite(argv[1]);
+ } else if (argc == 5 && strcmp(argv[2], "del") == 0) {
+ if (dbdelpair(argv[1], argv[3], argv[4]) == -1)
+ fail("dbdelpair()");
+ dbwrite(argv[1]);
+ } else {
+ usage(argv[0]);
+ }
+
+ dbcleanup();
+ return EXIT_SUCCESS;
+}
diff --git a/src/data.c b/src/data.c
@@ -0,0 +1,6 @@
+#include <stddef.h>
+
+unsigned char DejaVuSansMono_ttf[] = {
+#include "../data/DejaVuSansMono.h"
+};
+size_t DejaVuSansMono_ttf_size = sizeof(DejaVuSansMono_ttf);
diff --git a/src/main.c b/src/main.c
@@ -0,0 +1,41 @@
+#include <stdio.h>
+#include <raylib.h>
+#include "main.h"
+
+#define TESTSAVE "testdb"
+
+Save *save = NULL;
+
+int
+main(void) {
+ int w, h;
+ ui_init();
+ Tabs test = {
+ 2, 0, {{"Display", 0}, {"Minerals", 0}}
+ };
+
+ save = save_init(TESTSAVE);
+
+ while (!WindowShouldClose()) {
+ ui_clickable_update();
+
+ if (IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT)) {
+ /* AAAAAAAAAAHHHHHHHHHHHH. WHY NOT JUST USE KEY_1, KEY_2..! */
+ if (IsKeyPressed(KEY_ONE)) view_tabs.sel = 0;
+ else if (IsKeyPressed(KEY_TWO)) view_tabs.sel = 1;
+ else if (IsKeyPressed(KEY_THREE)) view_tabs.sel = 2;
+ else if (IsKeyPressed(KEY_FOUR)) view_tabs.sel = 3;
+ else if (IsKeyPressed(KEY_FIVE)) view_tabs.sel = 4;
+ else if (IsKeyPressed(KEY_SIX)) view_tabs.sel = 5;
+ }
+
+ BeginDrawing();
+ ClearBackground(COL_BG);
+ ui_draw_views();
+ view_drawers[view_tabs.sel]();
+ EndDrawing();
+ }
+
+ ui_deinit();
+ return 0;
+}
diff --git a/src/main.h b/src/main.h
@@ -0,0 +1,57 @@
+#include <stddef.h>
+#include <stdarg.h>
+#include <raylib.h>
+#include "struct.h"
+#include "style.h"
+#include "../db/db.h"
+
+#define ELEMS(array) (sizeof(array)/sizeof(array[0]))
+
+/* main.c */
+extern Save *save;
+
+/* str.c */
+char * vsmprintf(char *fmt, va_list args);
+char * smprintf(char *fmt, ...); /* return allocated string */
+char * vsbprintf(char *fmt, va_list args);
+char * sbprintf(char *fmt, ...); /* return string that is usable until next call */
+
+/* ui.c */
+#define VIEWS_MAX_WIDTH (UI_VIEW_LAST*100)
+#define VIEWS_HEIGHT 25
+#define WINDOW_TAB_HEIGHT 20
+extern Tabs view_tabs;
+extern void (*view_drawers[UI_VIEW_LAST])(void);
+void ui_init(void);
+void ui_deinit(void);
+void ui_print(int x, int y, Color col, char *format, ...);
+int ui_textsize(char *text);
+void ui_clickable_register(int x, int y, int w, int h, enum UiElements type, void *elem);
+void ui_clickable_handle(Vector2 mouse, MouseButton button, Clickable *clickable);
+void ui_clickable_update(void);
+void ui_draw_views(void);
+void ui_draw_border(int x, int y, int w, int h, int px);
+void ui_draw_tabs(Tabs *tabs, int x, int y, int w, int h);
+void ui_draw_tabbed_window(Tabs *tabs, int x, int y, int w, int h);
+void ui_draw_view_main(void);
+void ui_draw_view_colonies(void);
+void ui_draw_view_fleets(void);
+void ui_draw_view_design(void);
+void ui_draw_view_systems(void);
+void ui_draw_view_settings(void);
+
+/* system.c */
+Vector2 system_vectorize(Polar polar);
+Vector2 system_vectorize_around(Vector2 around, Polar polar);
+Polar system_polarize(Vector2 vector);
+Polar system_sum_polar(Polar absolute, Polar relative);
+Vector2 system_get_vector(char *system, char *body);
+Polar system_get_polar(char *system, char *body);
+
+/* save.c */
+Save * save_init(char *savedir);
+
+/* data.c */
+#include <stddef.h>
+extern unsigned char DejaVuSansMono_ttf[];
+extern size_t DejaVuSansMono_ttf_size;
diff --git a/src/save.c b/src/save.c
@@ -0,0 +1,17 @@
+#include <stdlib.h>
+#include <string.h>
+#include "main.h"
+
+Save *
+save_init(char *dir) {
+ Save *ret;
+
+ if (!dir || !(ret = malloc(sizeof(Save))))
+ return NULL;
+
+ ret->dir = strdup(dir);
+ ret->races = smprintf("%s/Races", dir);
+ ret->systems = smprintf("%s/Systems", dir);
+ ret->fleets = smprintf("%s/Fleets", dir);
+ return ret;
+};
diff --git a/src/str.c b/src/str.c
@@ -0,0 +1,59 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+char *
+vsmprintf(char *fmt, va_list args) {
+ va_list ap;
+ int size;
+ char *ret;
+
+ va_copy(ap, args);
+ size = vsnprintf(NULL, 0, fmt, ap) + 1;
+ va_end(ap);
+
+ if (size == 0) /* -1 */
+ return NULL;
+
+ ret = malloc(size);
+ if (!ret) return NULL;
+
+ va_copy(ap, args);
+ vsnprintf(ret, size, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char *
+smprintf(char *fmt, ...) {
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vsmprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char *
+vsbprintf(char *fmt, va_list args) {
+ static char *ret = NULL;
+ va_list ap;
+
+ free(ret);
+ va_copy(ap, args);
+ ret = vsmprintf(fmt, args);
+ va_end(ap);
+ return ret;
+}
+
+char *
+sbprintf(char *fmt, ...) {
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vsbprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
diff --git a/src/struct.h b/src/struct.h
@@ -0,0 +1,53 @@
+#include <stddef.h>
+
+typedef struct Save Save;
+struct Save {
+ /* db locator strings.
+ * dir the base, everything else is "dir/var" */
+ char *dir;
+ char *races;
+ char *systems;
+ char *fleets;
+};
+
+/* ui.c */
+#define TABS_MAX 16
+typedef struct Tabs Tabs;
+struct Tabs {
+ int n;
+ int sel;
+ struct {
+ char *name;
+ int w; /* 0 = fill */
+ } tabs[TABS_MAX];
+};
+
+enum UiViews {
+ UI_VIEW_MAIN,
+ UI_VIEW_COLONIES,
+ UI_VIEW_FLEETS,
+ UI_VIEW_DESIGN,
+ UI_VIEW_SYSTEMS,
+ UI_VIEW_SETTINGS,
+ UI_VIEW_LAST
+};
+
+enum UiElements {
+ UI_TAB,
+};
+
+#define CLICKABLE_MAX 64
+typedef struct Clickable Clickable;
+struct Clickable {
+ int x, y;
+ int w, h;
+ enum UiElements type;
+ void *elem;
+};
+
+/* system.c */
+typedef struct Polar Polar;
+struct Polar {
+ float r;
+ float theta;
+};
diff --git a/src/style.h b/src/style.h
@@ -0,0 +1,56 @@
+/* undefining default raylib colours (copied from raylib.h v4.1 */
+#undef LIGHTGRAY
+#undef GRAY
+#undef DARKGRAY
+#undef YELLOW
+#undef GOLD
+#undef ORANGE
+#undef PINK
+#undef RED
+#undef MAROON
+#undef GREEN
+#undef LIME
+#undef DARKGREEN
+#undef SKYBLUE
+#undef BLUE
+#undef DARKBLUE
+#undef PURPLE
+#undef VIOLET
+#undef DARKPURPLE
+#undef BEIGE
+#undef BROWN
+#undef DARKBROWN
+#undef WHITE
+#undef BLACK
+#undef BLANK
+#undef MAGENTA
+#undef RAYWHITE
+
+#define X_WHITE 0xe6e6e6ff
+#define X_AURORA_GREY 0x1e1e1eff
+#define X_AURORA_BLUE 0x00003cff
+#define X_DARKBLUE 0x00002cff
+#define X_AURORA_GREEN 0x649696ff
+
+#define UNHEX(col) ((Color){ \
+ ((col & (0xff << 24)) >> 24), \
+ ((col & (0xff << 16)) >> 16), \
+ ((col & (0xff << 8)) >> 8), \
+ (col & 0xff)})
+
+#define WHITE UNHEX(X_WHITE)
+#define AURORA_GREY UNHEX(X_AURORA_GREY)
+#define AURORA_BLUE UNHEX(X_AURORA_BLUE)
+#define DARKBLUE UNHEX(X_DARKBLUE)
+#define AURORA_GREEN UNHEX(X_AURORA_GREEN)
+
+/* colours */
+#define COL_FG WHITE
+#define COL_BG AURORA_BLUE
+#define COL_UNSELBG DARKBLUE
+#define COL_BORDER AURORA_GREY
+#define COL_INFO AURORA_GREEN
+#define COL_ORBIT AURORA_GREEN
+
+/* font */
+#define FONT_SIZE 10
diff --git a/src/system.c b/src/system.c
@@ -0,0 +1,61 @@
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+#include "raylib.h"
+#include "main.h"
+
+Vector2
+system_vectorize(Polar polar) {
+ return (Vector2) {
+ polar.r * cosf(polar.theta),
+ polar.r * sinf(polar.theta)
+ };
+}
+
+Vector2
+system_vectorize_around(Vector2 around, Polar polar) {
+ Vector2 relative = system_vectorize(polar);
+ return (Vector2) {
+ around.x + relative.x,
+ around.y + relative.y
+ };
+}
+
+Polar
+system_polarize(Vector2 vector) {
+ return (Polar) {
+ hypotf(vector.x, vector.y),
+ atan2f(vector.y, vector.x)
+ };
+}
+
+Polar
+system_sum_polar(Polar absolute, Polar relative) {
+ return system_polarize(
+ system_vectorize_around(system_vectorize(absolute), relative));
+}
+
+Vector2
+system_get_vector(char *system, char *body) {
+ return system_vectorize(system_get_polar(system, body));
+}
+
+Polar
+system_get_polar(char *system, char *body) {
+ Polar polar;
+ char *group, *dist, *parent;
+
+ group = sbprintf("%s/%s", system, body);
+ if (strcmp(dbget(save->systems, group, "type"), "Comet") == 0)
+ dist = dbget(save->systems, group, "curdist");
+ else
+ dist = dbget(save->systems, group, "dist");
+
+ parent = dbget(save->systems, group, "parent");
+ polar.r = strtof(dist, NULL);
+ polar.theta = strtof(dbget(save->systems, group, "curtheta"), NULL);
+ if (!parent)
+ return (Polar) {polar.r, polar.theta};
+ else
+ return system_sum_polar(system_get_polar(system, parent), polar);
+}
diff --git a/src/ui.c b/src/ui.c
@@ -0,0 +1,241 @@
+#include <raylib.h>
+#include "main.h"
+
+static Font font;
+static Clickable clickable[CLICKABLE_MAX];
+
+void (*view_drawers[UI_VIEW_LAST])(void) = {
+ [UI_VIEW_MAIN] = ui_draw_view_main,
+ [UI_VIEW_COLONIES] = ui_draw_view_colonies,
+ [UI_VIEW_FLEETS] = ui_draw_view_fleets,
+ [UI_VIEW_DESIGN] = ui_draw_view_design,
+ [UI_VIEW_SYSTEMS] = ui_draw_view_systems,
+ [UI_VIEW_SETTINGS] = ui_draw_view_settings,
+};
+
+Tabs view_tabs = {
+ UI_VIEW_LAST, 0, {{"Main", 0}, {"Colonies", 0}, {"Fleets", 0}, {"Design", 0},
+ {"Systems", 0}, {"Settings", 0}}
+};
+
+void
+ui_init(void) {
+ InitWindow(500, 500, "testing raylib");
+ SetWindowState(FLAG_WINDOW_RESIZABLE);
+ SetTargetFPS(30);
+
+ font = LoadFontFromMemory(".ttf",
+ DejaVuSansMono_ttf, DejaVuSansMono_ttf_size,
+ FONT_SIZE, NULL, 0);
+}
+
+void
+ui_deinit(void) {
+ CloseWindow();
+}
+
+void
+ui_print(int x, int y, Color col, char *fmt, ...) {
+ va_list ap;
+ Vector2 pos;
+
+ pos.x = x;
+ pos.y = y;
+ va_start(ap, fmt);
+ DrawTextEx(font, vsbprintf(fmt, ap), pos, (float)FONT_SIZE, FONT_SIZE/10, col);
+ va_end(ap);
+}
+
+int
+ui_textsize(char *text) {
+ return MeasureTextEx(font, text, FONT_SIZE, FONT_SIZE/10).x;
+}
+
+void
+ui_clickable_register(int x, int y, int w, int h, enum UiElements type, void *elem) {
+ int i;
+
+ for (i = 0; i < CLICKABLE_MAX; i++) {
+ if (!clickable[i].elem) {
+ clickable[i].x = x;
+ clickable[i].y = y;
+ clickable[i].w = w;
+ clickable[i].h = h;
+ clickable[i].type = type;
+ clickable[i].elem = elem;
+ return;
+ }
+ }
+
+ /* welp, we ran out */
+}
+
+void
+ui_clickable_handle(Vector2 mouse, MouseButton button, Clickable *clickable) {
+ Tabs *tabs;
+ int ftabw, fw, fn, tabw, x;
+ int i;
+
+ switch (clickable->type) {
+ case UI_TAB:
+ if (button != MOUSE_BUTTON_LEFT)
+ return;
+ tabs = clickable->elem;
+ for (fw = clickable->w, fn = i = 0; i < tabs->n; i++) {
+ if (!tabs->tabs[i].w)
+ fn++;
+ else
+ fw -= tabs->tabs[i].w;
+ }
+ ftabw = fw / fn;
+ for (i = 0, x = clickable->x; i < tabs->n; x += tabw, i++) {
+ if (i == tabs->n - 1)
+ tabw = clickable->x + clickable->w - x;
+ else if (!tabs->tabs[i].w)
+ tabw = ftabw;
+ else
+ tabw = tabs->tabs[i].w;
+ if (mouse.x >= x && mouse.x <= x + tabw) {
+ tabs->sel = i;
+ return;
+ }
+ }
+ break;
+ }
+}
+
+void
+ui_clickable_update(void) {
+ Vector2 mouse;
+ MouseButton button;
+ int i;
+
+ mouse = GetMousePosition();
+ /* I wish there was a: int GetMouseButton(void) */
+ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) button = MOUSE_BUTTON_LEFT;
+ else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) button = MOUSE_BUTTON_MIDDLE;
+ else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) button = MOUSE_BUTTON_RIGHT;
+ else button = -1;
+
+ for (i = 0; i < CLICKABLE_MAX; i++) {
+ if (clickable[i].elem &&
+ mouse.x >= clickable[i].x &&
+ mouse.x <= clickable[i].x + clickable[i].w &&
+ mouse.y >= clickable[i].y &&
+ mouse.y <= clickable[i].y + clickable[i].h)
+ ui_clickable_handle(mouse, button, &clickable[i]);
+ clickable[i].elem = NULL;
+ }
+}
+
+void
+ui_draw_views(void) {
+ int sw = GetScreenWidth();
+ if (sw > VIEWS_MAX_WIDTH) sw = VIEWS_MAX_WIDTH;
+ ui_draw_tabs(&view_tabs, 0, 0, sw, VIEWS_HEIGHT);
+}
+
+void
+ui_draw_border(int x, int y, int w, int h, int px) {
+ DrawRectangle(x, y, w, px, COL_BORDER); /* top */
+ DrawRectangle(x, y, px, h, COL_BORDER); /* left */
+ DrawRectangle(x, y + h - px, w, px, COL_BORDER); /* bottom */
+ DrawRectangle(x + w - px, y, px, h, COL_BORDER); /* right */
+}
+
+void
+ui_draw_tabs(Tabs *tabs, int x, int y, int w, int h) {
+ int fw, fn, ftabw;
+ int tabw;
+ int padx, pady;
+ int cx, selx = -1;
+ int i;
+
+ for (fw = w, fn = i = 0; i < tabs->n; i++) {
+ if (!tabs->tabs[i].w)
+ fn++;
+ else
+ fw -= tabs->tabs[i].w;
+ }
+
+ ftabw = fw / fn;
+ pady = (h - FONT_SIZE) / 2;
+
+ for (i = 0, cx = x; i < tabs->n; i++, cx += tabw) {
+ if (i == tabs->n - 1)
+ tabw = x + w - cx;
+ else if (!tabs->tabs[i].w)
+ tabw = ftabw;
+ else
+ tabw = tabs->tabs[i].w;
+ padx = (tabw - ui_textsize(tabs->tabs[i].name)) / 2;
+ if (i == tabs->sel)
+ selx = cx;
+ else
+ DrawRectangle(cx, y, tabw, h, COL_UNSELBG);
+ ui_print(cx + padx, y + pady, COL_FG, "%s", tabs->tabs[i].name);
+ DrawRectangle(cx + tabw - 1, y, 1, h, COL_BORDER);
+ }
+
+ if (tabs->sel != tabs->n - 1) {
+ if (!tabs->tabs[i].w)
+ tabw = ftabw;
+ else
+ tabw = w / tabs->n;
+ }
+
+ ui_draw_border(x, y, w, h, 1);
+ if (selx != -1) DrawRectangle(selx - 1, y + h - 1, tabw + 1, 1, COL_BG); /* undraw bottom border */
+ if (tabs->sel == 0) DrawRectangle(x, y + 1, 1, h - 1, COL_BG); /* undraw left border */
+ if (tabs->sel == tabs->n - 1) DrawRectangle(x + w - 1, y + 1, 1, h - 1, COL_BG); /* undraw right border */
+
+ ui_clickable_register(x, y, w, h, UI_TAB, tabs);
+}
+
+void
+ui_draw_tabbed_window(Tabs *tabs, int x, int y, int w, int h) {
+ ui_draw_tabs(tabs, x, y, w, WINDOW_TAB_HEIGHT);
+ ui_draw_border(x, y, w, h, 2);
+}
+
+void
+ui_draw_view_main(void) {
+ static Tabs window_tabs = {
+ 2, 0, {{"Display", 0}, {"Minerals", 0}}
+ };
+ ui_draw_tabbed_window(&window_tabs, 10, VIEWS_HEIGHT + 10, 200, 400);
+ ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Pannable body view here");
+}
+
+void
+ui_draw_view_colonies(void) {
+ ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Stars/colonies here");
+ ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 10, COL_FG, "Tabs here");
+ ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Management stuff here");
+}
+
+void
+ui_draw_view_fleets(void) {
+ ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Groups/fleets/subfleets/ships here");
+ ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 10, COL_FG, "Tabs here");
+ ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Management stuff here");
+}
+
+void
+ui_draw_view_design(void) {
+ ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Designations/classes here");
+ ui_print(GetScreenWidth() / 4, VIEWS_HEIGHT + 10, COL_FG, "Selectable components here");
+ ui_print((GetScreenWidth() / 4) * 2, VIEWS_HEIGHT + 10, COL_FG, "Selected components");
+ ui_print((GetScreenWidth() / 4) * 3, VIEWS_HEIGHT + 10, COL_FG, "Class info");
+}
+
+void
+ui_draw_view_systems(void) {
+ ui_print(10, GetScreenHeight() / 2, COL_FG, "System info/settings menu");
+ ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Pannable system view");
+}
+
+void
+ui_draw_view_settings(void) {
+ ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Settings here");
+}