commit ee48872cc8dac3654b54058e0a4e665da1e534d6 Author: anon <anon@anon.anon> Date: Fri Jan 3 16:20:48 2025 +0100 working demo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f47cb20 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.out diff --git a/documentation/reference.jpg b/documentation/reference.jpg new file mode 100644 index 0000000..d85dd13 Binary files /dev/null and b/documentation/reference.jpg differ diff --git a/examples/ncurses_columns.c b/examples/ncurses_columns.c new file mode 100644 index 0000000..6e36536 --- /dev/null +++ b/examples/ncurses_columns.c @@ -0,0 +1,51 @@ +// @BAKE gcc $@ -o $*.out -I../ $(pkg-config --cflags --libs ncurses) -lm +#include <stdio.h> +#include <ncurses.h> +#include "rect_layouts.h" + +WINDOW * windows[5] = { NULL, NULL, NULL, NULL, NULL }; + +void do_resize(void) { + rect_t parent = center(get_screen_rect(), scale(get_screen_rect(), 0.8)); + for (int i = 0; i < 5; i++) { + delwin(windows[i]); + // NOTE: we are actually redundantly recalculating, + // both for scaling and in the expansion of UNPACK; + // a temp var should be deployed; + // now, this is generally bad and should be avoided, + // however it *does work* and developer comfort is a top priority + windows[i] = newwin(UNPACK(next(rfloor(scalex(parent, 0.2)), i))); + refresh(); + } + + clear(); + wrefresh(stdscr); +} + +signed main(void) { + initscr(); + noecho(); + curs_set(0); + halfdelay(1); + + do_resize(); + + while(1) { + switch (wgetch(stdscr)) { + case KEY_RESIZE: do_resize(); + } + + for (int i = 0; i < 5; i++) { + int maxy, maxx; + getmaxyx(windows[i], maxy, maxx); + for (int h = 0; h < (maxy * maxx); h++) { + waddch(windows[i], '0' + i); + } + box(windows[i], ACS_VLINE, ACS_HLINE); + wrefresh(windows[i]); + } + } + + endwin(); + return 0; +} diff --git a/examples/raylib_diablo_like.c b/examples/raylib_diablo_like.c new file mode 100644 index 0000000..561aac0 --- /dev/null +++ b/examples/raylib_diablo_like.c @@ -0,0 +1,60 @@ +// @BAKE gcc $@ -o $*.out -I../ -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 +#include <raylib.h> +#include "rect_layouts.h" + +rect_t hotbar; +rect_t healt; +rect_t manna; +rect_t inventory; +rect_t inventory_inner; +rect_t inventory_slot; + +void do_resize(void) { + hotbar = ride(after(get_screen_rect(), 1), scaley(get_screen_rect(), 0.17f)); + healt = ride(after(get_screen_rect(), 1), scale(get_unit_rect(), hotbar.height * 1.2f)); + manna = paper(hotbar, healt); + inventory = stretchy(hotbar, paper(get_screen_rect(), scalex(get_screen_rect(), 0.5f))); + inventory_inner = buoyance(inventory, scale(inventory, 0.8f)); + inventory_slot = rock(inventory_inner, hang(inventory_inner, scale(get_unit_rect(), inventory_inner.width / 9))); +} + +signed main(void) { + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + InitWindow(600, 600, ""); + + do_resize(); + + while (!WindowShouldClose()) { + if (IsWindowResized()) { + do_resize(); + } + + BeginDrawing(); + DrawRectangleRec(get_screen_rect(), RAYWHITE); + DrawRectangleRec(inventory, BLACK); + DrawRectangleRec(inventory_inner, GRAY); + + for (int i = 0; i < 6; i++) { + for (int h = 0; h < 9; h++) { + DrawRectangleRec( + after(next(inventory_slot, h), i), + (Color) { + (unsigned char)(0 + ((i + h) * 10)), + (unsigned char)(0 + ((i + h) * 10)), + (unsigned char)(0 + ((i + h) * 10)), + 255, + } + ); + } + } + + DrawRectangleRec(hotbar, GRAY); + DrawRectangleRec(healt, RED); + DrawRectangleRec(manna, BLUE); + EndDrawing(); + } + + CloseWindow(); + + return 0; +} diff --git a/rect_layouts.h b/rect_layouts.h new file mode 100644 index 0000000..9811393 --- /dev/null +++ b/rect_layouts.h @@ -0,0 +1,404 @@ +#ifndef RECT_LAYOUTS_H +#define RECT_LAYOUTS_H +/* This header file defines with intuitive rectangle transformations. + * + * The intended purpose is to ease defining single screen interfaces. + * Layout engines are great, but very complex to implement and master. + * Static layouts on the otherhand tend to get very ugly, verbose and bug prone. + * The idea is to have a very minimalistic abstraction layer, + * with easy to visualize operations. + * This solves readability and typo-ing x to y, while being widely usable, + * adaptable or even reimplementable within just a few minutes. + * + * No doubt someone somewhere has adapted a similar approach before, + * however to the best of my knowledge this is the first attempt + * to normalize it into a library. + * In case I'm wrong, please throw me and email. + */ + +// TODO: further macro hell x, y, width and height so they can be user overwritten too +// TODO: stretch(x|y) naming is meh +// TODO: theres a logical inconsistency between ride-hang and rock-paper; either justify it or normalize it + + +// ### --------------- ### +// ### SPECIALIZATIONS ### +// ### --------------- ### +/* This has to go on top, because macros. Please read on. + */ + +#ifdef RAYLIB_H +# define rect_t Rectangle +static inline +rect_t get_screen_rect(void) { + return (rect_t) { + .x = 0, + .y = 0, + .width = (float)GetScreenWidth(), + .height = (float)GetScreenHeight(), + }; +} +#endif + + +// ### ------- ### +// ### General ### +// ### ------- ### + +/* Our internal rectangle representation. + * Feel free to overwrite it with whatever suits you, + * just #define alias it to `rect_t`. + */ +#ifndef rect_t +typedef struct rect_t { + float x, y, width, height; +} rect_t; +#endif + +#ifdef __NCURSES_H +# define UNPACK(r) (int)r.height, (int)r.width, (int)r.y, (int)r.x +static inline +rect_t get_screen_rect(void) { + return (rect_t) { + .x = 0, + .y = 0, + .width = (float)COLS, + .height = (float)LINES, + }; +} +#endif + +// tl;dr +static inline rect_t rfloor(rect_t r); +static inline rect_t get_unit_rect(void); +static inline rect_t scaley(rect_t a, float f); +static inline rect_t scalex(rect_t a, float f); +static inline rect_t scale(rect_t a, float f); +static inline rect_t balance(rect_t dest, rect_t source); +static inline rect_t buoyance(rect_t dest, rect_t source); +static inline rect_t center(rect_t dest, rect_t source); +static inline rect_t hang(rect_t dest, rect_t source); +static inline rect_t ride(rect_t dest, rect_t source); +static inline rect_t rock(rect_t dest, rect_t source); +static inline rect_t paper(rect_t dest, rect_t source); +static inline rect_t next(rect_t source, int n); +static inline rect_t after(rect_t source, int n); +static inline rect_t stretchy(rect_t dest, rect_t source); +static inline rect_t stretchx(rect_t dest, rect_t source); + + + +/* Floor every field of a rect. + * Useful if next() or after() create visible gaps. + * NOTE: we are not actually floaring so we dont depend on <math.h>, + * for our ends and purposes it should just werk⢠+ */ +static inline +rect_t rfloor(rect_t r) { + return (rect_t) { + .x = (long long)r.x, + .y = (long long)r.y, + .width = (long long)r.width, + .height = (long long)r.height, + }; +} + + +/* Return the easiest rect to transform. + * Coordinates (0, 0) are easy to shift. + * Size 1x1 is easy to scale. + * + * +-+ + * +-+ + */ +static inline +rect_t get_unit_rect(void) { + return (rect_t) { + .x = 0, + .y = 0, + .width = 1, + .height = 1, + }; +} + + +/* Modify the width by a factor. + * + * +-+ __\ +---+ + * +-+ / +---+ + */ +static inline +rect_t scalex(rect_t a, float f) { + return (rect_t) { + .x = a.x, + .y = a.y, + .width = a.width * f, + .height = a.height, + }; +} + + +/* Modify the height by a factor. + * + * +-+ __\ +-+ + * +-+ / | | + * +-+ + */ +static inline +rect_t scaley(rect_t a, float f) { + return (rect_t) { + .x = a.x, + .y = a.y, + .width = a.width, + .height = a.height * f, + }; +} + + +/* Modify the height and width by a factor. + * Exists because its judged to be a common operation. + * + * +-+ __\ +---+ + * +-+ / | | + * +---+ + */ +static inline +rect_t scale(rect_t a, float f) { + return (rect_t) { + .x = a.x, + .y = a.y, + .width = a.width * f, + .height = a.height * f, + }; +} + +/* Align to the middle horiontally + * + * +---+---+---+ + * | : | + * +---+ | + * | | | + * | | | + * +---+ | + * | : | + * +---+---+---+ + */ +static inline +rect_t balance(rect_t dest, rect_t source) { + return (rect_t) { + .x = dest.x + ((dest.width - source.width) / 2), + .y = source.y, + .width = source.width, + .height = source.height, + }; +} + +/* Align to the middle vertically + * + * +---+---+---+ + * | | | | + * | - | | - | + * | +---+ | + * | | + * | | + * | | + * +---+---+---+ + */ +static inline +rect_t buoyance(rect_t dest, rect_t source) { + return (rect_t) { + .x = source.x, + .y = dest.y + ((dest.height - source.height) / 2), + .width = source.width, + .height = source.height, + }; +} + + +/* Blance and Buoyance. Align to the middle vertically and horizontally. + * Exists because its judged to be a common operation. + * + * +-----------+ + * | : | + * | +---+ | + * | - | | - | + * | | | | + * | +---+ | + * | : | + * +-----------+ + */ +static inline +rect_t center(rect_t dest, rect_t source) { + return balance(dest, buoyance(dest, source)); +} + + +/* Dangles from the top. + * + * +---+-------+ + * | | | + * | | | + * +---+ | + * | | + * | | + * | | + * +-----------+ + */ +static inline +rect_t hang(rect_t dest, rect_t source) { + return (rect_t) { + .x = source.x, + .y = dest.y, + .width = source.width, + .height = source.height, + }; +} + + +/* Places on the top. + * + * +---+ + * | | + * | | + * +---+-------+ + * | | + * | | + * | | + * | | + * | | + * | | + * +-----------+ + */ +static inline +rect_t ride(rect_t dest, rect_t source) { + return (rect_t) { + .x = source.x, + .y = dest.y - source.height, + .width = source.width, + .height = source.height, + }; +} + + +/* Moves to the left. + * NOTE: this is a reference to the political compas, for easy memorization. + * + * +---+-------+ + * | | | + * | | | + * +---+ | + * | | + * | | + * | | + * +-----------+ + */ +static inline +rect_t rock(rect_t dest, rect_t source) { + return (rect_t) { + .x = dest.x, + .y = source.y, + .width = source.width, + .height = source.height, + }; +} + + +/* Moves to the right. + * NOTE: this is a reference to the political compas, for easy memorization. + * + * +-------+---+ + * | | | + * | | | + * | +---+ + * | | + * | | + * | | + * +-----------+ + */ +static inline +rect_t paper(rect_t dest, rect_t source) { + return (rect_t) { + .x = (dest.x + dest.width) - source.width, + .y = source.y, + .width = source.width, + .height = source.height, + }; +} + + +/* Gets the N-th horiontal neighbour. + * + * +---+---+ + * | | | + * | | | + * +---+---+ + */ +static inline +rect_t next(rect_t source, int n) { + return (rect_t) { + .x = source.x + (source.width * n), + .y = source.y, + .width = source.width, + .height = source.height, + }; +} + +/* Gets the N-th vectical neighbour. + * + * +---+ + * | | + * | | + * +---+ + * | | + * | | + * +---+ + */ +static inline +rect_t after(rect_t source, int n) { + return (rect_t) { + .x = source.x, + .y = source.y + (source.height * n), + .width = source.width, + .height = source.height, + }; +} + +static inline +rect_t stretchy(rect_t dest, rect_t source) { + return (dest.y > source.y) ? + (rect_t) { + .x = source.x, + .y = source.y, + .width = source.width, + .height = dest.y - source.y, + } + : + (rect_t) { + .x = source.x, + .y = dest.y + dest.height, + .width = source.width, + .height = source.height + (source.y - (dest.y + dest.height)), + } + ; +} + +static inline +rect_t stretchx(rect_t dest, rect_t source) { + return (dest.x > source.x) ? + (rect_t) { + .x = source.x, + .y = source.y, + .width = dest.x - source.x, + .height = source.height, + } + : + (rect_t) { + .x = dest.x + dest.width, + .y = source.y, + .width = source.width + (source.x - (dest.x + dest.width)), + .height = source.height, + } + ; +} + +#endif