diff --git a/assets/AdwaitaSans-Regular.ttf b/assets/AdwaitaSans-Regular.ttf new file mode 100644 index 0000000..6fcafd9 Binary files /dev/null and b/assets/AdwaitaSans-Regular.ttf differ diff --git a/src/application.cpp b/src/application.cpp new file mode 100644 index 0000000..5db71e2 --- /dev/null +++ b/src/application.cpp @@ -0,0 +1,81 @@ +#include "application.h" +#include "style.h" +#include "elements.h" +#include "simulation.h" +#include +#include + +namespace application { +namespace internal { +bool isSimulating{ false }; +} + +static void SetSimulatingButton(Clay_ElementId element, Clay_PointerData pointer, intptr_t data) { + if (pointer.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + internal::isSimulating = data == 1; + } +} + +static void RandomizeFieldButton(Clay_ElementId element, Clay_PointerData pointer, intptr_t data) { + if (pointer.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + simulation::InitializeRandom(2, 300); + } +} + +static void StepSimulationButton(Clay_ElementId element, Clay_PointerData pointer, intptr_t data) { + if (pointer.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { + simulation::Step(); + } +} + +static void StepControl() { + CLAY_AUTO_ID({ + .layout = { + .childGap = 16 + } + }) { + if (internal::isSimulating) { + elements::TextButton(CLAY_STRING("Pause"), style::warningButton, &SetSimulatingButton, false); + } else { + elements::TextButton(CLAY_STRING("Start"), style::proceedButton, &SetSimulatingButton, true); + elements::TextButton(CLAY_STRING("Step"), style::actionButton, &StepSimulationButton); + } + } +} + +static void PrimaryControls() { + static bool isOpen{ true }; + if (isOpen) { + CLAY_AUTO_ID(style::LeftPanelContainer(0, { + .layout = { + .sizing = { CLAY_SIZING_FIT(.18) }, + } + })) { + CLAY_AUTO_ID({ + .layout = { + .childGap = 16, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + }, + .clip = { + true, true, Clay_GetScrollOffset() + } + }) { + StepControl(); + elements::TextButton(CLAY_STRING("Randomize"), style::actionButton, &RandomizeFieldButton); + } + } + } +} + +Clay_RenderCommandArray RenderApplication() { + Clay_BeginLayout(); + CLAY(CLAY_ID("OuterContainer"), style::Window()) { + simulation::SetSimulationHovered(Clay_Hovered()); + PrimaryControls(); + } + return Clay_EndLayout(); +} + +void HandleEvent(SDL_Event event) { +} +} diff --git a/src/application.h b/src/application.h new file mode 100644 index 0000000..9d74ea0 --- /dev/null +++ b/src/application.h @@ -0,0 +1,12 @@ +#ifndef APPLICATION_H +#define APPLICATION_H + +#include +#include + +namespace application { +Clay_RenderCommandArray RenderApplication(); +void HandleEvent(SDL_Event event); +} + +#endif // !APPLICATION_H diff --git a/src/elements.cpp b/src/elements.cpp new file mode 100644 index 0000000..32a96f1 --- /dev/null +++ b/src/elements.cpp @@ -0,0 +1,36 @@ +#include "elements.h" +#include "style.h" +#include "resources.h" + +namespace elements { +void TextButton(Clay_String text, Clay_Color color, OnHoveredFn onHovered, intptr_t onHoveredData) { + Clay_Color hovered{ style::ToHoveredColor(color) }; + CLAY_AUTO_ID({ + .layout = { + .padding = style::buttonPadding, + .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }, + }, + .backgroundColor = Clay_Hovered() ? hovered : color, + .cornerRadius = style::buttonRadii, + .border = { style::ToHoveredColor(Clay_Hovered() ? hovered : color), CLAY_BORDER_ALL(1) }, + }) { + elements::Body(text, { + .textColor = style::TextColors(0), + .textAlignment = CLAY_TEXT_ALIGN_CENTER, + }); + Clay_OnHover(onHovered, onHoveredData); + } +} + +void Body(Clay_String string, Clay_TextElementConfig baseCfg) { + baseCfg.fontId = resources::FONT_DEFAULT; + baseCfg.fontSize = style::baseFontSize; + CLAY_TEXT(string, CLAY_TEXT_CONFIG(baseCfg)); +} + +void Header(Clay_String string, size_t header, Clay_TextElementConfig baseCfg) { + baseCfg.fontId = resources::FONT_BOLD; + baseCfg.fontSize = style::headerSizes[(header)-1]; + CLAY_TEXT(string, CLAY_TEXT_CONFIG(baseCfg)); +} +} diff --git a/src/elements.h b/src/elements.h new file mode 100644 index 0000000..fb1c1e9 --- /dev/null +++ b/src/elements.h @@ -0,0 +1,14 @@ +#ifndef ELEMENTS_H +#define ELEMENTS_H + +#include + +namespace elements { +typedef void(*OnHoveredFn)(Clay_ElementId element, Clay_PointerData pointer, intptr_t data); + +void TextButton(Clay_String text, Clay_Color color, OnHoveredFn onHovered, intptr_t onHoveredData = 0); +void Body(Clay_String string, Clay_TextElementConfig baseCfg); +void Header(Clay_String string, size_t header, Clay_TextElementConfig baseCfg); +} + +#endif // !ELEMENTS_H diff --git a/src/input.cpp b/src/input.cpp new file mode 100644 index 0000000..b6bb773 --- /dev/null +++ b/src/input.cpp @@ -0,0 +1,39 @@ +#include "input.h" + +namespace input { +Clay_Vector2 scrollMotion{ 0, 0 }; +bool shiftDown{ false }; +bool mouseButtonDown{ false }; + +void FrameStart() { + scrollMotion = { 0, 0 }; +} + +void HandleEvent(SDL_Event const &event) { + switch (event.type) { + case SDL_EVENT_MOUSE_WHEEL: + if (shiftDown) { + scrollMotion = (Clay_Vector2) { event.wheel.y * 2.f, -event.wheel.x * 5.f }; + } else { + scrollMotion = (Clay_Vector2) { -event.wheel.x * 2.f, event.wheel.y * 5.f }; + } + break; + case SDL_EVENT_MOUSE_MOTION: + Clay_SetPointerState((Clay_Vector2) { event.motion.x, event.motion.y }, mouseButtonDown); + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + if (event.button.button == SDL_BUTTON_LEFT) { + mouseButtonDown = event.button.down; + Clay_SetPointerState((Clay_Vector2) { event.button.x, event.button.y }, mouseButtonDown); + } + break; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + if (event.key.key == SDLK_LSHIFT || event.key.key == SDLK_RSHIFT) { + shiftDown = event.key.down; + } + break; + } +} +} diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..ddd2c09 --- /dev/null +++ b/src/input.h @@ -0,0 +1,15 @@ +#ifndef INPUT_H +#define INPUT_H + +#include +#include + +namespace input { +extern Clay_Vector2 scrollMotion; +extern bool shiftDown; +extern bool mouseButtonDown; +void FrameStart(); +void HandleEvent(SDL_Event const &event); +} + +#endif // !INPUT_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8d0d60a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,146 @@ +#define SDL_MAIN_HANDLED + +#include "application.h" +#include "input.h" +#include "resources.h" +#include "simulation.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr SDL_InitFlags sdlInitFlags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; + +SDL_Window *window = nullptr; +SDL_Renderer *renderer = nullptr; +int screenWidth = 1920, screenHeight = 1080; +bool running = true; +uint64_t clayMemorySize = 0; + +Clay_Arena clayPrimaryArena; + +Clay_SDL3RendererData backendData = { + nullptr, nullptr, nullptr +}; + +static +Clay_Dimensions MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { + TTF_Font **fonts = (TTF_Font**)userData; + TTF_Font *font = fonts[config->fontId]; + int width, height; + TTF_SetFontSize(font, config->fontSize); + if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "MeasureText failed to measure text %s", SDL_GetError()); + } + return (Clay_Dimensions) { (float)width, (float)height }; +} + +static +void HandleClayErrors(Clay_ErrorData data) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", data.errorText.chars); +} + +static inline +void LogOutputResolution() { + int w, h; + SDL_GetCurrentRenderOutputSize(renderer, &w, &h); + SDL_Log("output size: %i, %d", w, h); +} + +static inline +void InitSDL() { + SDL_SetHint(SDL_HINT_RENDER_LINE_METHOD, "3"); + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "SDL_Init failed: %s", SDL_GetError()); + exit(1); + } + if ((window = SDL_CreateWindow("Window", screenWidth, screenHeight, sdlInitFlags)) == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "SDL_CreateWindow failed: %s", SDL_GetError()); + exit(2); + } + if ((renderer = SDL_CreateRenderer(window, NULL)) == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "SDL_CreateRenderer failed: %s", SDL_GetError()); + exit(3); + } + if (!TTF_Init()) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "TTF_Init failed: %s", SDL_GetError()); + exit(4); + } + if ((resources::textEngine = TTF_CreateRendererTextEngine(renderer)) == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "TTF_CreateRendererTextEngine failed: %s", SDL_GetError()); + exit(5); + } +} + +static +void InitClay() { + clayMemorySize = Clay_MinMemorySize(); + clayPrimaryArena = Clay_CreateArenaWithCapacityAndMemory(clayMemorySize, SDL_malloc(clayMemorySize)); + Clay_Initialize(clayPrimaryArena, { (float)screenWidth, (float)screenHeight }, { HandleClayErrors }); + Clay_SetMeasureTextFunction(MeasureText, resources::fonts); + Clay_SetLayoutDimensions({ (float)screenWidth, (float)screenHeight }); + float x, y; + SDL_GetMouseState(&x, &y); + Clay_SetPointerState((Clay_Vector2) { x, y }, false); +} + +int main(int argc, char *argv[]) { + InitSDL(); + resources::LoadResources(); + LogOutputResolution(); + InitClay(); + backendData = (Clay_SDL3RendererData) { renderer, resources::textEngine, resources::fonts }; + SDL_Event event; + uint64_t startFrameTime = SDL_GetTicksNS(); + double deltaTime = 0.0; + while (running) { + std::srand(SDL_GetTicksNS()); + deltaTime = SDL_GetTicksNS() - startFrameTime; + startFrameTime = SDL_GetTicksNS(); + UiData_Clear(); + input::FrameStart(); + while (SDL_PollEvent(&event)) { + application::HandleEvent(event); + input::HandleEvent(event); + switch (event.type) { + case SDL_EVENT_QUIT: + running = false; + break; + case SDL_EVENT_WINDOW_RESIZED: + Clay_SetLayoutDimensions({ + (float)event.window.data1, + (float)event.window.data2 + }); + LogOutputResolution(); + break; + default: break; + } + } + Clay_UpdateScrollContainers(true, input::scrollMotion, deltaTime); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + simulation::Draw(renderer, 0.01); + Clay_RenderCommandArray commands{ application::RenderApplication() }; + SDL_Clay_RenderClayCommands(&backendData, &commands); + SDL_RenderPresent(renderer); + SDL_Delay(10); + } + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} diff --git a/src/resources.cpp b/src/resources.cpp new file mode 100644 index 0000000..845ef9f --- /dev/null +++ b/src/resources.cpp @@ -0,0 +1,32 @@ +#include "resources.h" +#include "style.h" +#include +#include +#include +#include + +namespace resources { +TTF_Font *fonts[FONT_MAX]; +TTF_TextEngine *textEngine = nullptr; + +static inline void LoadFonts() { + fonts[FONT_DEFAULT] = TTF_OpenFont("assets/AdwaitaSans-Regular.ttf", style::baseFontSize * 5); + if (fonts[FONT_DEFAULT] == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "TTF_OpenFont failed: Failed to load adwaita sans: %s", SDL_GetError()); + exit(6); + } + TTF_SetFontHinting(fonts[FONT_DEFAULT], TTF_HINTING_LIGHT_SUBPIXEL); + fonts[FONT_BOLD] = TTF_OpenFont("assets/AdwaitaSans-Regular.ttf", style::baseFontSize * 5); + if (fonts[FONT_BOLD] == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "TTF_OpenFont failed: Failed to load adwaita sans bold: %s", SDL_GetError()); + exit(6); + } + TTF_SetFontHinting(fonts[FONT_BOLD], TTF_HINTING_LIGHT_SUBPIXEL); + TTF_SetFontStyle(fonts[FONT_BOLD], TTF_STYLE_BOLD); + SDL_Log("LoadFonts: Success"); +} + +void LoadResources() { + LoadFonts(); +} +} diff --git a/src/resources.h b/src/resources.h new file mode 100644 index 0000000..ef12346 --- /dev/null +++ b/src/resources.h @@ -0,0 +1,19 @@ +#ifndef RESOURCES_H +#define RESOURCES_H + +#include + +namespace resources { +enum Font { + FONT_DEFAULT = 0, + FONT_BOLD = 1, + FONT_MAX +}; + +extern TTF_TextEngine *textEngine; +extern TTF_Font *fonts[FONT_MAX]; + +void LoadResources(); +} + +#endif // !RESOURCES_H diff --git a/src/simulation.cpp b/src/simulation.cpp new file mode 100644 index 0000000..0b97b33 --- /dev/null +++ b/src/simulation.cpp @@ -0,0 +1,192 @@ +#include "simulation.h" +#include "input.h" +#include +#include +#include +#include +#include +#include +#include + +namespace simulation { +std::set living{}; +static std::vector overpopulated{}; +static std::vector underpopulated{}; +static std::vector born{}; + +#ifdef __glibc_likely +#define likely(cond_) __glibc_likely(cond_) +#else +#define likely(cond_) (cond_) +#endif + +CellIterator::CellIterator(Cell begin, Cell end) +: state{ begin } , begin{ begin }, end{ end } {} + +CellIterator::CellIterator(Cell begin, Cell end, Cell state) +: state{ state } , begin{ begin }, end{ end } {} + +CellIterator &CellIterator::operator++() { + ++(this->state.x); + if (this->state.x == this->end.x) { + this->state.x = this->begin.x; + this->state.y = SDL_min(this->state.y + 1, this->end.y); + } + return *this; +} + +CellIterator &CellIterator::operator--() { + --(this->state.x); + if (this->state.x == this->begin.x) { + this->state.x = this->end.x - 1; + this->state.y = SDL_max(this->state.y - 1, this->begin.y); + } + return *this; +} + +bool CellIterator::operator==(CellIterator const &src) const { + return src.begin == this->begin && src.end == this->end && (src.state == this->state || src.at_end() == this->at_end()); +} + +bool CellIterator::operator!=(CellIterator const &src) const { + return src.begin != this->begin || src.end != this->end || (src.state != this->state && src.at_end() != this->at_end()); +} + +Cell const &CellIterator::operator*() const { + return state; +} + +bool CellIterator::at_end() const { + return this->state.y == this->end.y; +} + +static std::vector NeighborSet(Cell cell) { + std::vector out{}; + for (Cell const &c : CellRange{ { cell.x - 1, cell.y - 1 }, { cell.x + 2, cell.y + 2 } }) { + if (likely(c != cell)) { + out.push_back(c); + } + } + return out; +} + +static size_t CountNeighbors(Cell const &cell) { + size_t count{ 0 }; + for (Cell const &c : CellRange{ {cell.x - 1, cell.y - 1}, { cell.x + 2, cell.y + 2} }) { + if (c != cell && living.contains(c)) { + ++count; + } + } + return count; +} + +static void PopulateChanges() { + overpopulated.clear(); + underpopulated.clear(); + born.clear(); + // TODO: consider multithreading, this is highly parallelisable + std::ranges::copy_if(living, std::back_inserter(overpopulated), + [&](Cell const &c) -> bool { + size_t const neighbors{ CountNeighbors(c) }; + return neighbors > 3; + }); + std::ranges::copy_if(living, std::back_inserter(underpopulated), + [&](Cell const &c) -> bool { + size_t const neighbors{ CountNeighbors(c) }; + return neighbors < 2; + }); + for (Cell const &cell : living) { + std::vector neighbors{ NeighborSet(cell) }; + std::ranges::copy_if(neighbors, std::back_inserter(born), + [&](Cell const &c) -> bool { + size_t const neighbors{ CountNeighbors(c) }; + return !living.contains(c) && neighbors == 3; + }); + } +} + +void InitializeRandom(size_t livingChance, int64_t fillArea) { + living.clear(); + Cell itr{ 0, 0 }; + while (itr.y < fillArea) { + if (std::rand() % livingChance == 0 ) { + living.insert(itr); + } + ++itr.x; + if (itr.x == fillArea) { + itr.x = 0; + ++itr.y; + } + } + PopulateChanges(); +} + +void Step() { + for (Cell const &cell : underpopulated) { + living.erase(cell); + } + for (Cell const &cell : overpopulated) { + living.erase(cell); + } + for (Cell const &cell : born) { + living.insert(cell); + } + PopulateChanges(); +} + +static bool simulationHovered{ false }; +static SDL_FPoint viewOffset{ 0, 0 }; + +void Draw(SDL_Renderer *renderer, double cellSizePercent) { + if (simulationHovered) { + viewOffset.x += input::scrollMotion.x; + viewOffset.y += input::scrollMotion.y; + } + int w; + SDL_GetCurrentRenderOutputSize(renderer, &w, nullptr); + float const cellWidth = static_cast(w) * cellSizePercent; + SDL_FRect cellRect{ + 0, 0, cellWidth, cellWidth + }; + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + for (Cell const &cell : living) { + cellRect.x = (viewOffset.x + cell.x) * cellRect.w; + cellRect.y = (viewOffset.y + cell.y) * cellRect.h; + SDL_RenderFillRect(renderer, &cellRect); + } + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + for (Cell const &cell : overpopulated) { + cellRect.x = (viewOffset.x + cell.x) * cellRect.w; + cellRect.y = (viewOffset.y + cell.y) * cellRect.h; + SDL_RenderRect(renderer, &cellRect); + } + SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255); + for (Cell const &cell : underpopulated) { + cellRect.x = (viewOffset.x + cell.x) * cellRect.w; + cellRect.y = (viewOffset.y + cell.y) * cellRect.h; + SDL_RenderRect(renderer, &cellRect); + } + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + for (Cell const &cell : born) { + cellRect.x = (viewOffset.x + cell.x) * cellRect.w; + cellRect.y = (viewOffset.y + cell.y) * cellRect.h; + SDL_RenderRect(renderer, &cellRect); + } +} + +void SetSimulationHovered(bool value) { + simulationHovered = value; +} +bool operator==(Cell const &lhs, Cell const &rhs) { + return lhs.x == rhs.x && lhs.y == rhs.y; +} +bool operator!=(Cell const &lhs, Cell const &rhs) { + return lhs.x != rhs.x || lhs.y != rhs.y; +} + +bool operator<(Cell const &lhs, Cell const &rhs) { + uintptr_t hashl{ uintptr_t(lhs.x * 73856093) ^ uintptr_t(lhs.y * 19349663) }; + uintptr_t hashr{ uintptr_t(rhs.x * 73856093) ^ uintptr_t(rhs.y * 19349663) }; + return hashl < hashr; +} +} diff --git a/src/simulation.h b/src/simulation.h new file mode 100644 index 0000000..b0699ba --- /dev/null +++ b/src/simulation.h @@ -0,0 +1,48 @@ +#ifndef SIMULATION_H +#define SIMULATION_H + +#include +#include +#include +#include + +namespace simulation { +struct Cell { + int64_t x{ 0 }, y{ 0 }; + +}; + +class CellIterator { +friend class CellRange; +public: CellIterator(Cell begin, Cell end); + CellIterator &operator++(); + CellIterator &operator--(); + CellIterator &operator=(CellIterator const &src); + bool operator==(CellIterator const &src) const; + bool operator!=(CellIterator const &src) const; + Cell const &operator*() const; + bool at_end() const; +private: CellIterator(Cell begin, Cell end, Cell state); + Cell state{ 0, 0 }; + Cell begin{ 0, 0 }, end{ 0, 0 }; +}; + +class CellRange { +private: + Cell beginCell{ 0, 0 }, endCell{ 0, 0 }; +public: CellRange(Cell begin, Cell end) : beginCell{ begin }, endCell{ end } {} + CellIterator begin() { return CellIterator{ beginCell, endCell }; } + CellIterator end() { return CellIterator{ beginCell, endCell, endCell }; } +}; + +void InitializeRandom(size_t livingChance, int64_t fillArea); +void Step(); +void Draw(SDL_Renderer *renderer, double cellSizePercent); +void SetSimulationHovered(bool value); + +bool operator==(Cell const &lhs, Cell const &rhs); +bool operator!=(Cell const &lhs, Cell const &rhs); +bool operator<(Cell const &lhs, Cell const &rhs); +} + +#endif // !SIMULATION_H diff --git a/src/style.cpp b/src/style.cpp new file mode 100644 index 0000000..6816639 --- /dev/null +++ b/src/style.cpp @@ -0,0 +1,87 @@ +#include "style.h" +#include "resources.h" +#include + +namespace style { +Clay_ElementDeclaration ListContainer(size_t depth, Clay_ElementDeclaration baseCfg) { + baseCfg.border = { + PanelBorder(depth), + CLAY_BORDER_ALL(2) + }; + baseCfg.cornerRadius = defaultRadiusAll; + return baseCfg; +} + +Clay_ElementDeclaration PanelContainer(size_t depth, Clay_ElementDeclaration baseCfg) { + baseCfg.backgroundColor = PanelBackground(depth); + baseCfg.border = { + PanelBorder(depth), + CLAY_BORDER_OUTSIDE(2) + }; + baseCfg.cornerRadius = defaultRadiusAll; + baseCfg.layout.padding = CLAY_PADDING_ALL(16); + return baseCfg; +} + +Clay_ElementDeclaration LeftPanelContainer(size_t depth, Clay_ElementDeclaration baseCfg) { + baseCfg = PanelContainer(depth, baseCfg); + baseCfg.border.width = { 0, 2, 2, 2, 0 }; + baseCfg.cornerRadius = { 0, defaultRadius, 0, defaultRadius }; + baseCfg.layout.sizing.height = CLAY_SIZING_GROW(); + return baseCfg; +} + +Clay_ElementDeclaration RightPanelContainer(size_t depth, Clay_ElementDeclaration baseCfg) { + baseCfg = PanelContainer(depth, baseCfg); + baseCfg.border.width = { 2, 0, 2, 2, 0 }; + baseCfg.cornerRadius = { defaultRadius, 0, defaultRadius, 0 }; + baseCfg.layout.sizing.height = CLAY_SIZING_GROW(); + return baseCfg; +} + +Clay_Color PanelBackground(size_t idx) { + return { + 255*panelBackground[idx], + 255*panelBackground[idx], + 255*panelBackground[idx], + 255 + }; +} + +Clay_Color PanelBorder(size_t idx) { + return { + 255*panelBorder[idx], + 255*panelBorder[idx], + 255*panelBorder[idx], + 255 + }; +} + +Clay_Color TextColors(size_t idx) { + return { + 255*textColorsP[idx], + 255*textColorsP[idx], + 255*textColorsP[idx], + 255 + }; +} + +Clay_ElementDeclaration Window() { + return { + .layout = { + .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, + .padding = CLAY_PADDING_ALL(0), + .childGap = 0, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + }, + }; +} + +Clay_Color ToHoveredColor(Clay_Color color) { + float avg = (color.r + color.g + color.b) / 3.f; + color.r = (color.r - avg) * 0.8f + avg - 30; + color.g = (color.g - avg) * 0.8f + avg - 30; + color.b = (color.b - avg) * 0.8f + avg - 30; + return color; +} +} diff --git a/src/style.h b/src/style.h new file mode 100644 index 0000000..b395821 --- /dev/null +++ b/src/style.h @@ -0,0 +1,109 @@ +#ifndef STYLE_H +#define STYLE_H + +#include +#include + +namespace style { +//////////////////////////////////// +// WINDOW STYLE +//////////////////////////////////// + +constexpr uint16_t windowPadding{ 10 }; + +//////////////////////////////////// +// CONTAINER STYLE +//////////////////////////////////// + +constexpr uint16_t containerGap{ 10 }; +constexpr double defaultRadius{ 5.0 }; + +constexpr float panelBackground[] = { + .2f, .3f, .4f +}; +constexpr float panelBorder[] = { + .3f, .4f, .5f +}; + +constexpr Clay_Padding panelPadding = { + 24, 24, + 24, 24, +}; + +Clay_ElementDeclaration ListContainer(size_t depth, Clay_ElementDeclaration baseCfg); +Clay_ElementDeclaration PanelContainer(size_t depth, Clay_ElementDeclaration baseCfg); +Clay_ElementDeclaration LeftPanelContainer(size_t depth, Clay_ElementDeclaration baseCfg); + +//////////////////////////////////// +// TEXT STYLE +//////////////////////////////////// + +constexpr float paragraphGap = 10; +constexpr uint16_t baseFontSize = 16; +constexpr float textColorsP[] = { + 0.9f, 0.9f, 0.9f +}; + +constexpr uint16_t headerSizes[] = { + 64, 32, + 28, 16 +}; + +//////////////////////////////////// +// BUTTONS +//////////////////////////////////// + +constexpr Clay_Color warningButton = { + 177, 56, 52, 255 +}; +constexpr Clay_Color proceedButton = { + 49, 181, 99, 255 +}; +constexpr Clay_Color actionButton = { + 49, 142, 181, 255 +}; + +constexpr Clay_Padding buttonPadding = { + 24, 24, + 4, 4, +}; +constexpr Clay_CornerRadius buttonRadii = { + 3, 3, 3, 3 +}; + +constexpr Clay_Color dieColors[] = { + { 230, 184, 48, 255 }, + { 177, 56, 52, 255 }, + { 115, 177, 52, 255 }, + { 52, 177, 125, 255 }, + { 52, 177, 176, 255 }, + { 52, 93, 177, 255 }, + { 177, 52, 140, 255 }, + { 95, 52, 177, 255 }, +}; + +//////////////////////////////////// +// COMPILATIONS +// | Functions and expressions that combine styling data from the settings above. +//////////////////////////////////// + +constexpr Clay_Sizing layoutExpand = { + .width = CLAY_SIZING_GROW(0), + .height = CLAY_SIZING_GROW(0) +}; + +constexpr Clay_CornerRadius defaultRadiusAll = { + defaultRadius, defaultRadius, + defaultRadius, defaultRadius +}; + +Clay_Color PanelBackground(size_t idx); +Clay_Color PanelBorder(size_t idx); +Clay_Color TextColors(size_t idx); +Clay_Color WindowBackground(); +Clay_ElementDeclaration Window(); +Clay_Color ToHoveredColor(Clay_Color color); + +} + +#endif // !STYLE_H