diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9f46a8..7c536c8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,14 +19,17 @@ jobs: with: submodules: 'recursive' - - name: Compile mousebuttons example - run: make -C examples/mousebuttons/ + - name: Compile Examples + run: | + mkdir build/ + make -C examples/mousebuttons/ + make -C examples/shapes/ + make -C examples/ttf/ + make -C examples/cookieclicker/ DESTDIR=../../build shell: bash - - name: Compile shapes example - run: make -C examples/shapes/ - shell: bash - - - name: Compile ttf example - run: make -C examples/ttf/ - shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: examples-${{ runner.os }} + path: build/ diff --git a/examples/cookieclicker/main.c b/examples/cookieclicker/main.c new file mode 100644 index 0000000..5451643 --- /dev/null +++ b/examples/cookieclicker/main.c @@ -0,0 +1,135 @@ +#include "fenster.h" +#include + +struct AutoClicker { + const char* name; + __uint128_t cost, cps, owned; + int unlocked, bonus; +}; + +static struct AutoClicker autoclickers[] = { + {"Cursor", 15, 1, 0, 0, 1}, + {"Grandma", 100, 4, 0, 0, 1}, + {"Farm", 1100, 8, 0, 0, 1}, + {"Mine", 12000, 47, 0, 0, 1}, + {"Factory", 130000, 260, 0, 0, 1}, + {"Bank", 1400000, 1400, 0, 0, 1}, + {"Temple", 20000000, 7800, 0, 0, 1}, + {"Wizard Tower", 330000000, 44000, 0, 0, 1}, + {"Shipment", 5000000000, 260000, 0, 0, 1} +}; + +int fs = 0; +uint32_t bonus; + +int run() { + struct fenster f = {.title = "Cookie Clicker", .width = 800, .height = 600}; + fenster_open(&f); + + FensterFontList fonts = fenster_loadfontlist(); + FensterFont* font = fenster_loadfont(fonts.paths[0]); + + __uint128_t cookies = 0, last_time = 0, cookies_per_sec = 0; + float cookie_size = 60.f, cookie_grow_rate = 1.1f, cookie_max_size = 100.f; + int cookie_growing = 0; + int bonus = 0; + + while (fenster_loop(&f) == 0 && f.keys[27] == 0) { + fenster_fill(&f, 0x2A2A2A); + + int hovering_cursor = 1; + int cookie_x = f.width / 2, cookie_y = f.height / 2; + + if (cookie_growing) { + cookie_size = fminf(cookie_size * cookie_grow_rate, cookie_max_size); + } else { + cookie_size = 60.f; + } + fenster_drawcirc(&f, cookie_x, cookie_y, (int)cookie_size, 0x8B4513); + + if (fenster_point_in_circle(f.mpos[0], f.mpos[1], cookie_x, cookie_y, (int)cookie_size)) { + hovering_cursor = 2; + if (f.mclick[0]) { + cookies += 1 + (autoclickers[0].owned * autoclickers[0].bonus); + cookie_growing = 0; + } else { + cookie_growing = 1; + } + } else { + cookie_growing = 0; + } + + int64_t current_time = f.lastsync; + if (current_time - last_time >= 1000) { + cookies_per_sec = 0; + for (int i = 0; i < 9; i++) { + cookies_per_sec += autoclickers[i].owned * autoclickers[i].cps * autoclickers[i].bonus; + } + bonus = (int)(sqrt(cookies_per_sec) / 10) > 0 ? (int)(sqrt(cookies_per_sec) / 10) : 1; + cookies += cookies_per_sec * bonus; + last_time = current_time; + } + + vsformat("Cookies: %llu \\n Per Second: %llu", (unsigned long long)cookies, (unsigned long long)cookies_per_sec); + if (bonus > 1) { + vsformat_concat("\\c0x3299a8 \\s16 *%d", bonus); + } + fenster_drawtext(&f, font, vsbuff, 10, 30); + + int button_w = 200, button_h = 80, start_y = 100; + for (int i = 0; i < 9; i++) { + int x = 10, y = start_y + i * (button_h + 10); + int button_color = 0x404040; + + if (cookies >= autoclickers[i].cost && !autoclickers[i].unlocked) { + autoclickers[i].unlocked = 1; + } + + if (autoclickers[i].unlocked) { + if (cookies >= autoclickers[i].cost) { + if (fenster_point_in_rect(f.mpos[0], f.mpos[1], x, y, button_w, button_h)) { + button_color = 0x517554; + hovering_cursor = 2; + if (f.mclick[0]) { + cookies -= autoclickers[i].cost; + autoclickers[i].owned++; + autoclickers[i].bonus = (int)(sqrt(autoclickers[i].owned) / 2) > 0 ? (int)(sqrt(autoclickers[i].owned) / 2) : 1; + autoclickers[i].cost = autoclickers[i].cost * 115 / 100; + } + } else { + button_color = 0x475c49; + } + } + fenster_drawrec(&f, x, y, button_w, button_h, button_color); + + vsformat("\\c0xA9A9A9 %s", autoclickers[i].name); + if (autoclickers[i].bonus > 1) { + vsformat_concat("\\c0x3299a8 *%d", autoclickers[i].bonus); + } + vsformat_concat("\\n \\c0xFFFFFF Cost: %llu\\n Owned: %llu", + (unsigned long long)autoclickers[i].cost, + (unsigned long long)autoclickers[i].owned); + + fenster_drawtext(&f, font, vsbuff, x + 10, y + 10); + } + } + + fenster_cursor(&f, hovering_cursor); + fenster_sync(&f, 60); + if (f.keysp[70] == 1) fenster_fullscreen(&f, fs ^= 1); + } + + fenster_freefont(font); + fenster_freefontlist(&fonts); + fenster_close(&f); + return 0; +} + +#if defined(_WIN32) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow) { + (void)hInstance, (void)hPrevInstance, (void)pCmdLine, (void)nCmdShow; + return run(); +} +#else +int main() { return run(); } +#endif diff --git a/examples/cookieclicker/makefile b/examples/cookieclicker/makefile new file mode 100644 index 0000000..e80d616 --- /dev/null +++ b/examples/cookieclicker/makefile @@ -0,0 +1,22 @@ +CFLAGS ?= -mavx2 -lm -DUSE_FONTS -O3 -flto -Wall -Wextra -std=c99 +DESTDIR ?= . + +ifeq ($(OS),Windows_NT) + CC = gcc + LDFLAGS = -mwindows -lgdi32 +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + LDFLAGS = -framework Cocoa + else + LDFLAGS = -lX11 + endif +endif + +all: main + +main: main.c ../../src/fenster/fenster.h + $(CC) main.c -I../../src/fenster/ -o $(DESTDIR)/$@ $(CFLAGS) $(LDFLAGS) + +clean: + rm -f main diff --git a/examples/shapes/lines.c b/examples/shapes/lines.c index 5579a91..c06c146 100644 --- a/examples/shapes/lines.c +++ b/examples/shapes/lines.c @@ -39,10 +39,12 @@ static int run() { fenster_drawline(&f, f.width, 0, mouseX, mouseY, GREEN); fenster_drawline(&f, 0, f.height, mouseX, mouseY, WHITE); fenster_drawline(&f, f.width, f.height, mouseX, mouseY, WHITE); - - fenster_drawtext(&f, font, vsformat("Frame: %d", frame), 10, 10); - - fenster_drawtext(&f, font, vsformat("Press F for fullscreen | ESC to exit | Mouse: (%d, %d)", mouseX, mouseY), 10, f.height - 30); + + vsformat("Frame: %d", frame); + fenster_drawtext(&f, font, vsbuff, 10, 10); + + vsformat("Press F for fullscreen | ESC to exit | Mouse: (%d, %d)", mouseX, mouseY); + fenster_drawtext(&f, font, vsbuff, 10, f.height - 30); fenster_sync(&f, 60); if (f.keysp[70] == 1) fenster_fullscreen(&f, fs ^= 1); diff --git a/examples/ttf/main.c b/examples/ttf/main.c index b8c3296..5c9d7c0 100644 --- a/examples/ttf/main.c +++ b/examples/ttf/main.c @@ -15,11 +15,14 @@ static int run() { fenster_fill(&f, 0); fenster_drawtext(&f, font, "\\c0x526D82 Hello \\s32 \\c0xDDE6ED BIG \\s16 \\c0x526D82 World \\n From FensterB!", 10, 10); - fenster_drawtext(&f, font, vsformat("\\s16 \\c0xFFFFFF Press F to %s Full screen", fs == 0 ? "enter" : "leave"), 0, 200); - fenster_drawtext(&f, font, vsformat("\\c%d To save CPU cycles, we\\n reprint this text only\\n when window is resized.", rand()), 0, 100); + vsformat("\\s16 \\c0xFFFFFF Press F to %s Full screen", fs == 0 ? "enter" : "leave"); + fenster_drawtext(&f, font, vsbuff, 0, 200); + vsformat("\\c%d To save CPU cycles, we\\n reprint this text only\\n when window is resized.", rand()); + fenster_drawtext(&f, font, vsbuff, 0, 100); } - fenster_drawtext(&f, font, vsformat("\\b%d \\s16 Frame: %d", rand(), frame), 0, f.height-16); + vsformat("\\b%d \\s16 Frame: %d", rand(), frame); + fenster_drawtext(&f, font, vsbuff, 0, f.height-16); fenster_sync(&f, 30); if (f.keysp[70] == 1) fenster_fullscreen(&f, fs ^= 1); diff --git a/src/fenster/fenster_linux.h b/src/fenster/fenster_linux.h index be1a132..4b595c8 100644 --- a/src/fenster/fenster_linux.h +++ b/src/fenster/fenster_linux.h @@ -8,6 +8,7 @@ static Atom wm_state; static Atom wm_fullscreen; static Cursor cursors[6]; static int cursors_initialized = 0; +static int current_cursor_type = 1; // clang-format on FENSTER_API int fenster_open(struct fenster *f) { @@ -174,6 +175,7 @@ FENSTER_API void fenster_fullscreen(struct fenster *f, int enabled) { } FENSTER_API void fenster_cursor(struct fenster *f, int type) { + if (type == current_cursor_type) return; if (!cursors_initialized) { cursors[0] = 0; // None/hidden cursors[1] = XCreateFontCursor(f->dpy, XC_left_ptr); // Normal arrow @@ -196,7 +198,8 @@ FENSTER_API void fenster_cursor(struct fenster *f, int type) { // Set the cursor from our pre-created cursors XDefineCursor(f->dpy, f->w, cursors[type]); } - + + current_cursor_type = type; XFlush(f->dpy); } #endif /* FENSTER_LINUX_H */ diff --git a/src/fenster/fenster_mac.h b/src/fenster/fenster_mac.h index c4e6032..fd90d9f 100644 --- a/src/fenster/fenster_mac.h +++ b/src/fenster/fenster_mac.h @@ -13,6 +13,7 @@ extern id const NSDefaultRunLoopMode; extern id const NSApp; extern id NSCursor; static id cursors[6]; +static int current_cursor_type = 1; static void fenster_window_resize(id v, SEL s, id note) { (void)s; @@ -223,6 +224,7 @@ FENSTER_API void fenster_fullscreen(struct fenster *f, int enabled) { } FENSTER_API void fenster_cursor(struct fenster *f, int type) { + if (type == current_cursor_type) return; // Initialize cursors on first use if (!cursors[1]) { // Hide cursor (NULL represents hidden cursor) @@ -248,6 +250,7 @@ FENSTER_API void fenster_cursor(struct fenster *f, int type) { // Set and push the new cursor msg(void, cursors[type], "set"); } + current_cursor_type = type; } #endif /* FENSTER_MAC_H */ diff --git a/src/fenster/fenster_windows.h b/src/fenster/fenster_windows.h index 3f9d4e0..6a35664 100644 --- a/src/fenster/fenster_windows.h +++ b/src/fenster/fenster_windows.h @@ -16,6 +16,7 @@ static WINDOWPLACEMENT g_wpPrev = { static HCURSOR cursors[6]; static int cursors_initialized = 0; +static int current_cursor_type = 1; typedef struct BINFO { BITMAPINFOHEADER bmiHeader; @@ -221,6 +222,7 @@ FENSTER_API void fenster_fullscreen(struct fenster *f, int enabled) { } FENSTER_API void fenster_cursor(struct fenster *f, int type) { + if (type == current_cursor_type) return; // Initialize cursors on first use if (!cursors_initialized) { cursors[0] = NULL; // Will be used for hidden cursor @@ -253,5 +255,6 @@ FENSTER_API void fenster_cursor(struct fenster *f, int type) { wc.hCursor = cursors[type]; SetClassLongPtr(f->hwnd, GCLP_HCURSOR, (LONG_PTR)cursors[type]); } + current_cursor_type = type; } #endif /* FENSTER_WINDOWS_H */ diff --git a/src/fenster_addons.h b/src/fenster_addons.h index 66ff8d0..d1ae48e 100644 --- a/src/fenster_addons.h +++ b/src/fenster_addons.h @@ -17,15 +17,25 @@ #define PI 3.14159265358979323846 -const char* vsformat(const char* formato, ...) { - static char buffer[16 * 1024]; +#define VSBUFF_SIZE (16 * 1024) +char vsbuff[VSBUFF_SIZE]; + +void vsformat(const char* fmt, ...) { va_list args; - va_start(args, formato); - vsnprintf(buffer, sizeof(buffer), formato, args); + va_start(args, fmt); + vsnprintf(vsbuff, VSBUFF_SIZE, fmt, args); va_end(args); +} - return buffer; +void vsformat_concat(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + size_t len = strlen(vsbuff); + if (len < VSBUFF_SIZE - 1) { + vsnprintf(vsbuff + len, VSBUFF_SIZE - len, fmt, args); + } + va_end(args); } static inline void vector_fill(uint32_t* buf, size_t count, uint32_t color) { @@ -217,4 +227,15 @@ static inline void fenster_fill(struct fenster *f, uint32_t color) { vector_fill(f->buf, total_pixels, color); } +static inline int fenster_point_in_circle(int x, int y, int cx, int cy, int radius) { + int dx = x - cx; + int dy = y - cy; + return dx * dx + dy * dy <= radius * radius; +} + +static inline int fenster_point_in_rect(int x, int y, int rect_x, int rect_y, int rect_width, int rect_height) { + return x >= rect_x && x <= rect_x + rect_width && + y >= rect_y && y <= rect_y + rect_height; +} + #endif /* FENSTER_ADDONS_H */ diff --git a/src/fenster_font.h b/src/fenster_font.h index 66c1b76..1f67d03 100644 --- a/src/fenster_font.h +++ b/src/fenster_font.h @@ -2,6 +2,8 @@ #define FENSTER_FONT_H #define STB_TRUETYPE_IMPLEMENTATION +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb/stb_rect_pack.h" #include "stb/stb_truetype.h" #include "fenster/fenster.h" #include @@ -152,15 +154,18 @@ FensterFontList fenster_loadfontlist(void) { } } #elif defined(_WIN32) - char path[MAX_PATH]; - if (GetEnvironmentVariable("SYSTEMROOT", path, MAX_PATH)) { - strcat(path, "\\Fonts\\*.ttf"); + char base_path[MAX_PATH]; + if (GetEnvironmentVariable("SYSTEMROOT", base_path, MAX_PATH)) { + strcat(base_path, "\\Fonts"); + char search_path[MAX_PATH]; + sprintf(search_path, "%s\\*.ttf", base_path); + WIN32_FIND_DATA fd; - HANDLE h = FindFirstFile(path, &fd); + HANDLE h = FindFirstFile(search_path, &fd); if (h != INVALID_HANDLE_VALUE) { char full[MAX_PATH]; do { - sprintf(full, "%s\\Fonts\\%s", path, fd.cFileName); + sprintf(full, "%s\\%s", base_path, fd.cFileName); add_font_path(&fonts, full); } while (FindNextFile(h, &fd)); FindClose(h);