Skip to content

Instantly share code, notes, and snippets.

@linkdd
Created July 30, 2025 02:19
Show Gist options
  • Save linkdd/03389d8907c0cef7d07f551865b54d8f to your computer and use it in GitHub Desktop.
Save linkdd/03389d8907c0cef7d07f551865b54d8f to your computer and use it in GitHub Desktop.

Revisions

  1. linkdd created this gist Jul 30, 2025.
    16 changes: 16 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    # See

    - Clay: https://github.com/nicbarker/clay
    - Lexy: https://lexy.foonathan.net/

    # What is this?

    I made a parser for a CSS dialect (with far less features) to style my C++ game UI (which is made with Clay).

    Why? Well, plenty of reasons why not, so I'll just say:

    - for the fun
    - because maintaining the UI style in C++ was a pain
    - for the FUN

    Should you do this? Absolutely not.
    138 changes: 138 additions & 0 deletions example.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,138 @@
    element#root {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: grow(0, 0);
    layout-direction: left-to-right;
    image: 'textures/backgrounds/main-menu.png';
    background-color: rgba(0, 0, 0, 255);
    }

    element#gap {
    layout-sizing-width: 5%;
    layout-sizing-height: grow(0, 0);
    }

    element#menu {
    layout-sizing-width: 15%;
    layout-sizing-height: grow(0, 0);
    layout-direction: top-to-bottom;
    layout-padding: 16;
    layout-child-gap: 24;
    background-color: rgba(16, 16, 16, 196);
    border-width: outside(2);
    border-color: rgba(96, 96, 96, 255);
    }

    element#container {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: grow(0, 0);
    layout-padding: 64;
    }

    element#window {
    layout-sizing-width: 100%;
    layout-sizing-height: grow(0, 0);
    layout-direction: top-to-bottom;
    layout-padding: 16;
    layout-child-gap: 16;
    background-color: rgba(16, 16, 16, 196);
    border-width: outside(2);
    border-color: rgba(96, 96, 96, 255);
    }

    element#form {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: grow(0, 0);
    layout-direction: top-to-bottom;
    layout-child-alignment-x: center;
    layout-child-alignment-y: top;
    layout-padding: 8;
    layout-child-gap: 8;
    }

    element#form-field {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: fit(0, 0);
    layout-direction: left-to-right;
    layout-child-alignment-x: left;
    layout-child-alignment-y: center;
    layout-padding: 8;
    layout-child-gap: 8;
    }

    text#form-field-label {
    font-face: 'fonts/Unitblock.ttf';
    font-size: 18;
    color: rgba(255, 193, 140, 255);
    }

    text#form-field-error {
    font-face: 'fonts/Unitblock.ttf';
    font-size: 18;
    color: rgba(218, 109, 66, 255);
    }

    input#form-field-control {
    &.container {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: 36;
    layout-child-alignment-x: left;
    layout-child-alignment-y: center;
    layout-padding: 8;
    background-color: rgba(16, 16, 16, 255);
    border-width: outside(2);
    border-color: rgba(96, 96, 96, 255);

    &:hover {
    border-color: rgba(132, 36, 12, 255);
    }

    &:active {
    border-color: rgba(218, 109, 66, 255);
    }
    }

    &.content {
    font-face: 'fonts/Unitblock.ttf';
    font-size: 18;
    color: rgba(204, 204, 204, 255);
    }
    }

    element#form-button-group {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: fit(0, 0);
    layout-direction: left-to-right;
    layout-child-alignment-x: center;
    layout-child-alignment-y: center;
    layout-padding: 8;
    layout-child-gap: 64;
    }

    button#primary {
    &.container {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: 48;
    layout-child-alignment-x: center;
    layout-child-alignment-y: center;
    background-color: rgba(86, 50, 50, 255);

    &:hover {
    background-color: rgba(132, 36, 12, 255);
    }

    &:active {
    background-color: rgba(218, 109, 66, 255);
    }
    }

    &.label {
    font-face: 'fonts/Unitblock.ttf';
    font-size: 24;
    color: rgba(255, 193, 140, 255);
    }
    }

    element#separator {
    layout-sizing-width: grow(0, 0);
    layout-sizing-height: grow(0, 0);
    }
    20 changes: 20 additions & 0 deletions parser.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    #include <lexy/action/parse.hpp>
    #include <lexy/input/string_input.hpp>
    #include <lexy_ext/report_error.hpp>

    #include <engine/ui/styles/parser.hpp>


    namespace engine::ui::styles::parser {
    stylesheet eval(manager& ui_manager, const std::string& source) {
    auto input = lexy::string_input(source);
    auto result = lexy::parse<document>(input, ui_manager, lexy_ext::report_error);

    if (result.has_value()) {
    return result.value();
    }
    else {
    throw std::runtime_error{"Failed to parse stylesheet"};
    }
    }
    }
    1,510 changes: 1,510 additions & 0 deletions parser.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1510 @@
    #pragma once

    #include <cstdint>

    #include <sstream>
    #include <string>

    #include <filesystem>
    #include <utility>

    #include <functional>
    #include <vector>

    #include <lexy/dsl.hpp>
    #include <lexy/callback.hpp>

    #include <clay.h>

    #include <engine/ui/styles/types.hpp>
    #include <engine/ui/manager.hpp>


    namespace engine::ui::styles::parser {
    namespace dsl = lexy::dsl;

    namespace basic {
    // MARK: identifier
    struct identifier {
    static constexpr auto rule = dsl::identifier(
    dsl::ascii::alpha,
    dsl::ascii::alpha_digit_underscore / dsl::lit_c<'-'>
    );

    static constexpr auto value = lexy::as_string<std::string>;
    };

    // MARK: boolean
    struct boolean {
    struct true_ {
    static constexpr auto rule = LEXY_LIT("true");
    static constexpr auto value = lexy::constant(true);
    };

    struct false_ {
    static constexpr auto rule = LEXY_LIT("false");
    static constexpr auto value = lexy::constant(false);
    };

    static constexpr auto rule = dsl::p<true_> | dsl::p<false_>;
    static constexpr auto value = lexy::forward<bool>;
    };

    // MARK: float
    struct float_ {
    static constexpr auto rule
    = dsl::sign
    + dsl::integer<int>
    + dsl::opt(dsl::lit_c<'.'> >> dsl::integer<int>)
    ;

    static constexpr auto value = lexy::callback<float>(
    [](lexy::minus_sign, int i, int d) -> float {
    auto result = 0.0f;

    auto oss = std::ostringstream{};
    oss << "-" << i << "." << d;

    auto iss = std::istringstream{oss.str()};
    iss >> result;

    return result;
    },
    [](lexy::minus_sign, int i, lexy::nullopt) -> float {
    return -static_cast<float>(i);
    },
    [](lexy::plus_sign, int i, int d) -> float {
    auto result = 0.0f;

    auto oss = std::ostringstream{};
    oss << i << "." << d;

    auto iss = std::istringstream{oss.str()};
    iss >> result;

    return result;
    },
    [](lexy::plus_sign, int i, lexy::nullopt) -> float {
    return static_cast<float>(i);
    },
    [](int i, int d) -> float {
    auto result = 0.0f;

    auto oss = std::ostringstream{};
    oss << i << "." << d;

    auto iss = std::istringstream{oss.str()};
    iss >> result;

    return result;
    },
    [](int i, lexy::nullopt) -> float {
    return static_cast<float>(i);
    }
    );
    };

    // MARK: color
    struct color {
    static constexpr auto rule
    = LEXY_LIT("rgba")
    + dsl::parenthesized(dsl::times<4>(dsl::p<float_>, dsl::sep(dsl::comma)))
    ;

    static constexpr auto value = lexy::construct<Clay_Color>;
    };

    // MARK: str
    struct str {
    static constexpr auto rule = dsl::single_quoted(dsl::ascii::print);
    static constexpr auto value = lexy::as_string<std::string>;
    };
    }

    namespace property_set {
    template <typename T>
    struct as_property {
    struct _property_value {
    static constexpr auto rule = T::property_value_rule;
    static constexpr auto value = T::property_value_eval;
    };

    static constexpr auto rule
    = T::property_name_rule
    >> dsl::lit_c<':'>
    >> dsl::p<_property_value>
    >> dsl::lit_c<';'>
    ;

    using property_value_type = std::pair<T, typename T::property_value_type>;
    static constexpr auto value = lexy::callback<property_value_type>(
    [](typename T::property_value_type value) {
    return std::make_pair(T{}, value);
    }
    );
    };

    // MARK: elt props
    struct element_properties {
    struct _layout_sizing_axis {
    using property_value_type = Clay_SizingAxis;

    struct fit_value {
    static constexpr auto rule
    = LEXY_LIT("fit")
    >> dsl::parenthesized(dsl::times<2>(dsl::p<basic::float_>, dsl::sep(dsl::comma)))
    ;

    static constexpr auto value = lexy::callback<property_value_type>(
    [](float min, float max) {
    return CLAY_SIZING_FIT(min, max);
    }
    );
    };

    struct grow_value {
    static constexpr auto rule
    = LEXY_LIT("grow")
    >> dsl::parenthesized(dsl::times<2>(dsl::p<basic::float_>, dsl::sep(dsl::comma)))
    ;

    static constexpr auto value = lexy::callback<property_value_type>(
    [](float min, float max) {
    return CLAY_SIZING_GROW(min, max);
    }
    );
    };

    struct percent_value {
    static constexpr auto rule
    = dsl::p<basic::float_>
    + LEXY_LIT("%")
    ;

    static constexpr auto value = lexy::callback<property_value_type>(
    [](float percent) {
    return CLAY_SIZING_PERCENT(percent / 100.0f);
    }
    );
    };

    struct fixed_value {
    static constexpr auto rule = dsl::p<basic::float_>;

    static constexpr auto value = lexy::callback<property_value_type>(
    [](float size) {
    return CLAY_SIZING_FIXED(size);
    }
    );
    };

    static constexpr auto property_value_rule
    = dsl::p<fit_value>
    | dsl::p<grow_value>
    | dsl::peek(dsl::p<percent_value>) >> dsl::p<percent_value>
    | dsl::peek(dsl::p<fixed_value>) >> dsl::p<fixed_value>
    ;

    static constexpr auto property_value_eval
    = lexy::forward<property_value_type>;
    };

    struct layout_sizing_width : public _layout_sizing_axis {
    static constexpr auto property_name_rule = LEXY_LIT("layout-sizing-width");
    };
    using prop_layout_sizing_width = as_property<layout_sizing_width>;

    struct layout_sizing_height : public _layout_sizing_axis {
    static constexpr auto property_name_rule = LEXY_LIT("layout-sizing-height");
    };
    using prop_layout_sizing_height = as_property<layout_sizing_height>;

    struct layout_padding {
    using property_value_type = Clay_Padding;

    static constexpr auto property_name_rule = LEXY_LIT("layout-padding");
    static constexpr auto property_value_rule = dsl::integer<uint16_t>;
    static constexpr auto property_value_eval
    = lexy::callback<property_value_type>([](uint16_t padding) {
    return CLAY_PADDING_ALL(padding);
    });
    };
    using prop_layout_padding = as_property<layout_padding>;

    struct layout_padding_left {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("layout-padding-left");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_padding_left = as_property<layout_padding_left>;

    struct layout_padding_right {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("layout-padding-right");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_padding_right = as_property<layout_padding_right>;

    struct layout_padding_top {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("layout-padding-top");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_padding_top = as_property<layout_padding_top>;

    struct layout_padding_bottom {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("layout-padding-bottom");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_padding_bottom = as_property<layout_padding_bottom>;


    struct layout_child_gap {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("layout-child-gap");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_child_gap = as_property<layout_child_gap>;


    struct layout_child_alignment_x {
    using property_value_type = Clay_LayoutAlignmentX;

    struct left_value {
    static constexpr auto rule = LEXY_LIT("left");
    static constexpr auto value = lexy::constant(CLAY_ALIGN_X_LEFT);
    };

    struct center_value {
    static constexpr auto rule = LEXY_LIT("center");
    static constexpr auto value = lexy::constant(CLAY_ALIGN_X_CENTER);
    };

    struct right_value {
    static constexpr auto rule = LEXY_LIT("right");
    static constexpr auto value = lexy::constant(CLAY_ALIGN_X_RIGHT);
    };

    static constexpr auto property_name_rule = LEXY_LIT("layout-child-alignment-x");
    static constexpr auto property_value_rule
    = dsl::p<left_value>
    | dsl::p<center_value>
    | dsl::p<right_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_child_alignment_x = as_property<layout_child_alignment_x>;

    struct layout_child_alignment_y {
    using property_value_type = Clay_LayoutAlignmentY;

    struct top_value {
    static constexpr auto rule = LEXY_LIT("top");
    static constexpr auto value = lexy::constant(CLAY_ALIGN_Y_TOP);
    };

    struct center_value {
    static constexpr auto rule = LEXY_LIT("center");
    static constexpr auto value = lexy::constant(CLAY_ALIGN_Y_CENTER);
    };

    struct bottom_value {
    static constexpr auto rule = LEXY_LIT("bottom");
    static constexpr auto value = lexy::constant(CLAY_ALIGN_Y_BOTTOM);
    };

    static constexpr auto property_name_rule = LEXY_LIT("layout-child-alignment-y");
    static constexpr auto property_value_rule
    = dsl::p<top_value>
    | dsl::p<center_value>
    | dsl::p<bottom_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_child_alignment_y = as_property<layout_child_alignment_y>;


    struct layout_direction {
    using property_value_type = Clay_LayoutDirection;

    struct left_to_right_value {
    static constexpr auto rule = LEXY_LIT("left-to-right");
    static constexpr auto value = lexy::constant(CLAY_LEFT_TO_RIGHT);
    };

    struct top_to_bottom_value {
    static constexpr auto rule = LEXY_LIT("top-to-bottom");
    static constexpr auto value = lexy::constant(CLAY_TOP_TO_BOTTOM);
    };

    static constexpr auto property_name_rule = LEXY_LIT("layout-direction");
    static constexpr auto property_value_rule
    = dsl::p<left_to_right_value>
    | dsl::p<top_to_bottom_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_layout_direction = as_property<layout_direction>;


    struct background_color {
    using property_value_type = Clay_Color;

    static constexpr auto property_name_rule = LEXY_LIT("background-color");
    static constexpr auto property_value_rule = dsl::p<basic::color>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_background_color = as_property<background_color>;


    struct corner_radius {
    using property_value_type = Clay_CornerRadius;

    static constexpr auto property_name_rule = LEXY_LIT("corner-radius");
    static constexpr auto property_value_rule = dsl::times<4>(dsl::p<basic::float_>);
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_corner_radius = as_property<corner_radius>;

    struct corner_radius_top_left {
    using property_value_type = float;

    static constexpr auto property_name_rule = LEXY_LIT("corner-radius-top-left");
    static constexpr auto property_value_rule = dsl::p<basic::float_>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_corner_radius_top_left = as_property<corner_radius_top_left>;

    struct corner_radius_top_right {
    using property_value_type = float;

    static constexpr auto property_name_rule = LEXY_LIT("corner-radius-top-right");
    static constexpr auto property_value_rule = dsl::p<basic::float_>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_corner_radius_top_right = as_property<corner_radius_top_right>;

    struct corner_radius_bottom_left {
    using property_value_type = float;

    static constexpr auto property_name_rule = LEXY_LIT("corner-radius-bottom-left");
    static constexpr auto property_value_rule = dsl::p<basic::float_>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_corner_radius_bottom_left = as_property<corner_radius_bottom_left>;

    struct corner_radius_bottom_right {
    using property_value_type = float;

    static constexpr auto property_name_rule = LEXY_LIT("corner-radius-bottom-right");
    static constexpr auto property_value_rule = dsl::p<basic::float_>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_corner_radius_bottom_right = as_property<corner_radius_bottom_right>;


    struct aspect_ratio {
    using property_value_type = Clay_AspectRatioElementConfig;

    static constexpr auto property_name_rule = LEXY_LIT("aspect-ratio");
    static constexpr auto property_value_rule = dsl::p<basic::float_>;
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_aspect_ratio = as_property<aspect_ratio>;


    struct image {
    using property_value_type = std::filesystem::path;

    static constexpr auto property_name_rule = LEXY_LIT("image");
    static constexpr auto property_value_rule = dsl::p<basic::str>;
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_image = as_property<image>;


    struct floating_offset {
    using property_value_type = Clay_Vector2;

    static constexpr auto property_name_rule = LEXY_LIT("floating-offset");
    static constexpr auto property_value_rule = dsl::times<2>(dsl::p<basic::float_>);
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_floating_offset = as_property<floating_offset>;

    struct floating_expand {
    using property_value_type = Clay_Dimensions;

    static constexpr auto property_name_rule = LEXY_LIT("floating-expand");
    static constexpr auto property_value_rule = dsl::times<2>(dsl::p<basic::float_>);
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_floating_expand = as_property<floating_expand>;

    struct floating_zindex {
    using property_value_type = int16_t;

    static constexpr auto property_name_rule = LEXY_LIT("floating-zindex");
    static constexpr auto property_value_rule = dsl::integer<int16_t>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_floating_zindex = as_property<floating_zindex>;

    struct _floating_attach_point_type {
    using property_value_type = Clay_FloatingAttachPointType;

    struct left_top_value {
    static constexpr auto rule = LEXY_LIT("left-top");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_LEFT_TOP);
    };

    struct left_center_value {
    static constexpr auto rule = LEXY_LIT("left-center");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_LEFT_CENTER);
    };

    struct left_bottom_value {
    static constexpr auto rule = LEXY_LIT("left-bottom");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_LEFT_BOTTOM);
    };

    struct center_top_value {
    static constexpr auto rule = LEXY_LIT("center-top");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_CENTER_TOP);
    };

    struct center_center_value {
    static constexpr auto rule = LEXY_LIT("center-center");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_CENTER_CENTER);
    };

    struct center_bottom_value {
    static constexpr auto rule = LEXY_LIT("center-bottom");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_CENTER_BOTTOM);
    };

    struct right_top_value {
    static constexpr auto rule = LEXY_LIT("right-top");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_RIGHT_TOP);
    };

    struct right_center_value {
    static constexpr auto rule = LEXY_LIT("right-center");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_RIGHT_CENTER);
    };

    struct right_bottom_value {
    static constexpr auto rule = LEXY_LIT("right-bottom");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_POINT_RIGHT_BOTTOM);
    };

    static constexpr auto property_value_rule
    = dsl::p<left_top_value>
    | dsl::p<left_center_value>
    | dsl::p<left_bottom_value>
    | dsl::p<center_top_value>
    | dsl::p<center_center_value>
    | dsl::p<center_bottom_value>
    | dsl::p<right_top_value>
    | dsl::p<right_center_value>
    | dsl::p<right_bottom_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };

    struct floating_attach_point_element : public _floating_attach_point_type {
    static constexpr auto property_name_rule = LEXY_LIT("floating-attach-point-element");
    };
    using prop_floating_attach_point_element = as_property<floating_attach_point_element>;

    struct floating_attach_point_parent : public _floating_attach_point_type {
    static constexpr auto property_name_rule = LEXY_LIT("floating-attach-point-parent");
    };
    using prop_floating_attach_point_parent = as_property<floating_attach_point_parent>;

    struct floating_pointer_capture_mode {
    using property_value_type = Clay_PointerCaptureMode;

    struct capture_value {
    static constexpr auto rule = LEXY_LIT("capture");
    static constexpr auto value = lexy::constant(CLAY_POINTER_CAPTURE_MODE_CAPTURE);
    };

    struct passthrough_value {
    static constexpr auto rule = LEXY_LIT("passthrough");
    static constexpr auto value = lexy::constant(CLAY_POINTER_CAPTURE_MODE_PASSTHROUGH);
    };


    static constexpr auto property_name_rule = LEXY_LIT("floating-pointer-capture-mode");
    static constexpr auto property_value_rule
    = dsl::p<capture_value>
    | dsl::p<passthrough_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_floating_pointer_capture_mode = as_property<floating_pointer_capture_mode>;

    struct floating_attach_to {
    using property_value_type = Clay_FloatingAttachToElement;

    struct none_value {
    static constexpr auto rule = LEXY_LIT("none");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_TO_NONE);
    };

    struct parent_value {
    static constexpr auto rule = LEXY_LIT("parent");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_TO_PARENT);
    };

    struct element_value {
    static constexpr auto rule = LEXY_LIT("element");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_TO_ELEMENT_WITH_ID);
    };

    struct root_value {
    static constexpr auto rule = LEXY_LIT("root");
    static constexpr auto value = lexy::constant(CLAY_ATTACH_TO_ROOT);
    };

    static constexpr auto property_name_rule = LEXY_LIT("floating-attach-to");
    static constexpr auto property_value_rule
    = dsl::p<none_value>
    | dsl::p<parent_value>
    | dsl::p<element_value>
    | dsl::p<root_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_floating_attach_to = as_property<floating_attach_to>;

    struct floating_clip_to {
    using property_value_type = Clay_FloatingClipToElement;

    struct none_value {
    static constexpr auto rule = LEXY_LIT("none");
    static constexpr auto value = lexy::constant(CLAY_CLIP_TO_NONE);
    };

    struct parent_value {
    static constexpr auto rule = LEXY_LIT("parent");
    static constexpr auto value = lexy::constant(CLAY_CLIP_TO_ATTACHED_PARENT);
    };

    static constexpr auto property_name_rule = LEXY_LIT("floating-clip-to");
    static constexpr auto property_value_rule
    = dsl::p<none_value>
    | dsl::p<parent_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_floating_clip_to = as_property<floating_clip_to>;


    struct clip_horizontal {
    using property_value_type = bool;

    static constexpr auto property_name_rule = LEXY_LIT("clip-horizontal");
    static constexpr auto property_value_rule = dsl::p<basic::boolean>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_clip_horizontal = as_property<clip_horizontal>;

    struct clip_vertical {
    using property_value_type = bool;

    static constexpr auto property_name_rule = LEXY_LIT("clip-vertical");
    static constexpr auto property_value_rule = dsl::p<basic::boolean>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_clip_vertical = as_property<clip_vertical>;

    struct clip_child_offset {
    using property_value_type = Clay_Vector2;

    static constexpr auto property_name_rule = LEXY_LIT("clip-child-offset");
    static constexpr auto property_value_rule = dsl::times<2>(dsl::p<basic::float_>);
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_clip_child_offset = as_property<clip_child_offset>;


    struct border_color {
    using property_value_type = Clay_Color;

    static constexpr auto property_name_rule = LEXY_LIT("border-color");
    static constexpr auto property_value_rule = dsl::p<basic::color>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_color = as_property<border_color>;

    struct border_width {
    using property_value_type = Clay_BorderWidth;

    struct outside_value {
    static constexpr auto rule
    = LEXY_LIT("outside")
    + dsl::parenthesized(dsl::integer<uint16_t>)
    ;

    static constexpr auto value = lexy::callback<property_value_type>(
    [](uint16_t width) -> property_value_type {
    return CLAY_BORDER_OUTSIDE(width);
    }
    );
    };

    struct all_value {
    static constexpr auto rule
    = LEXY_LIT("all")
    + dsl::parenthesized(dsl::integer<uint16_t>)
    ;

    static constexpr auto value = lexy::callback<property_value_type>(
    [](uint16_t width) -> property_value_type {
    return CLAY_BORDER_ALL(width);
    }
    );
    };

    static constexpr auto property_name_rule = LEXY_LIT("border-width");
    static constexpr auto property_value_rule
    = dsl::peek(dsl::p<outside_value>) >> dsl::p<outside_value>
    | dsl::peek(dsl::p<all_value>) >> dsl::p<all_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_width = as_property<border_width>;

    struct border_width_left {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("border-width-left");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_width_left = as_property<border_width_left>;

    struct border_width_right {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("border-width-right");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_width_right = as_property<border_width_right>;

    struct border_width_top {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("border-width-top");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_width_top = as_property<border_width_top>;

    struct border_width_bottom {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("border-width-bottom");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_width_bottom = as_property<border_width_bottom>;

    struct border_width_between_children {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("border-width-between-children");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_border_width_between_children = as_property<border_width_between_children>;

    static constexpr auto rule = dsl::partial_combination(
    dsl::p<prop_layout_sizing_width>,
    dsl::p<prop_layout_sizing_height>,
    dsl::p<prop_layout_padding>,
    dsl::p<prop_layout_padding_left>,
    dsl::p<prop_layout_padding_right>,
    dsl::p<prop_layout_padding_top>,
    dsl::p<prop_layout_padding_bottom>,
    dsl::p<prop_layout_child_gap>,
    dsl::p<prop_layout_child_alignment_x>,
    dsl::p<prop_layout_child_alignment_y>,
    dsl::p<prop_layout_direction>,
    dsl::p<prop_background_color>,
    dsl::p<prop_corner_radius>,
    dsl::p<prop_corner_radius_top_left>,
    dsl::p<prop_corner_radius_top_right>,
    dsl::p<prop_corner_radius_bottom_left>,
    dsl::p<prop_corner_radius_bottom_right>,
    dsl::p<prop_aspect_ratio>,
    dsl::p<prop_image>,
    dsl::p<prop_floating_offset>,
    dsl::p<prop_floating_expand>,
    dsl::p<prop_floating_zindex>,
    dsl::p<prop_floating_attach_point_element>,
    dsl::p<prop_floating_attach_point_parent>,
    dsl::p<prop_floating_pointer_capture_mode>,
    dsl::p<prop_floating_attach_to>,
    dsl::p<prop_floating_clip_to>,
    dsl::p<prop_clip_horizontal>,
    dsl::p<prop_clip_vertical>,
    dsl::p<prop_clip_child_offset>,
    dsl::p<prop_border_color>,
    dsl::p<prop_border_width>,
    dsl::p<prop_border_width_left>,
    dsl::p<prop_border_width_right>,
    dsl::p<prop_border_width_top>,
    dsl::p<prop_border_width_bottom>,
    dsl::p<prop_border_width_between_children>
    );

    using property_setter = std::function<void(manager&, element_style&)>;
    using property_setters = std::vector<property_setter>;

    static constexpr auto value = lexy::fold_inplace<property_setters>(
    property_setters{},
    [](property_setters& setters, prop_layout_sizing_width::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.sizing.width = value;
    });
    },
    [](property_setters& setters, prop_layout_sizing_height::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.sizing.height = value;
    });
    },
    [](property_setters& setters, prop_layout_padding::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.padding = value;
    });
    },
    [](property_setters& setters, prop_layout_padding_left::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.padding.left = value;
    });
    },
    [](property_setters& setters, prop_layout_padding_right::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.padding.right = value;
    });
    },
    [](property_setters& setters, prop_layout_padding_top::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.padding.top = value;
    });
    },
    [](property_setters& setters, prop_layout_padding_bottom::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.padding.bottom = value;
    });
    },
    [](property_setters& setters, prop_layout_child_gap::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.childGap = value;
    });
    },
    [](property_setters& setters, prop_layout_child_alignment_x::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.childAlignment.x = value;
    });
    },
    [](property_setters& setters, prop_layout_child_alignment_y::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.childAlignment.y = value;
    });
    },
    [](property_setters& setters, prop_layout_direction::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.layout.layoutDirection = value;
    });
    },
    [](property_setters& setters, prop_background_color::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.backgroundColor = value;
    });
    },
    [](property_setters& setters, prop_corner_radius::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.cornerRadius = value;
    });
    },
    [](property_setters& setters, prop_corner_radius_top_left::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.cornerRadius.topLeft = value;
    });
    },
    [](property_setters& setters, prop_corner_radius_top_right::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.cornerRadius.topRight = value;
    });
    },
    [](property_setters& setters, prop_corner_radius_bottom_left::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.cornerRadius.bottomLeft = value;
    });
    },
    [](property_setters& setters, prop_corner_radius_bottom_right::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.cornerRadius.bottomRight = value;
    });
    },
    [](property_setters& setters, prop_aspect_ratio::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.aspectRatio = value;
    });
    },
    [](property_setters& setters, prop_image::property_value_type property) {
    setters.push_back([property](manager& ui_manager, element_style& decl) {
    auto [_, value] = property;
    auto texture_id = ui_manager.add_texture_to_atlas(value);
    decl.image.imageData = reinterpret_cast<void*>(static_cast<intptr_t>(texture_id));
    });
    },
    [](property_setters& setters, prop_floating_offset::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.offset = value;
    });
    },
    [](property_setters& setters, prop_floating_expand::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.expand = value;
    });
    },
    [](property_setters& setters, prop_floating_zindex::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.zIndex = value;
    });
    },
    [](property_setters& setters, prop_floating_attach_point_element::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.attachPoints.element = value;
    });
    },
    [](property_setters& setters, prop_floating_attach_point_parent::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.attachPoints.parent = value;
    });
    },
    [](property_setters& setters, prop_floating_pointer_capture_mode::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.pointerCaptureMode = value;
    });
    },
    [](property_setters& setters, prop_floating_attach_to::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.attachTo = value;
    });
    },
    [](property_setters& setters, prop_floating_clip_to::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.floating.clipTo = value;
    });
    },
    [](property_setters& setters, prop_clip_horizontal::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.clip.horizontal = value;
    });
    },
    [](property_setters& setters, prop_clip_vertical::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.clip.vertical = value;
    });
    },
    [](property_setters& setters, prop_clip_child_offset::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.clip.childOffset = value;
    });
    },
    [](property_setters& setters, prop_border_color::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.color = value;
    });
    },
    [](property_setters& setters, prop_border_width::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.width = value;
    });
    },
    [](property_setters& setters, prop_border_width_left::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.width.left = value;
    });
    },
    [](property_setters& setters, prop_border_width_right::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.width.right = value;
    });
    },
    [](property_setters& setters, prop_border_width_top::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.width.top = value;
    });
    },
    [](property_setters& setters, prop_border_width_bottom::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.width.bottom = value;
    });
    },
    [](property_setters& setters, prop_border_width_between_children::property_value_type property) {
    setters.push_back([property](manager&, element_style& decl) {
    auto [_, value] = property;
    decl.border.width.betweenChildren = value;
    });
    }
    );
    };

    // MARK: txt props
    struct text_properties {
    struct color {
    using property_value_type = Clay_Color;

    static constexpr auto property_name_rule = LEXY_LIT("color");
    static constexpr auto property_value_rule = dsl::p<basic::color>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_color = as_property<color>;

    struct font_face {
    using property_value_type = std::filesystem::path;

    static constexpr auto property_name_rule = LEXY_LIT("font-face");
    static constexpr auto property_value_rule = dsl::p<basic::str>;
    static constexpr auto property_value_eval = lexy::construct<property_value_type>;
    };
    using prop_font_face = as_property<font_face>;

    struct font_size {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("font-size");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_font_size = as_property<font_size>;

    struct letter_spacing {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("letter-spacing");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_letter_spacing = as_property<letter_spacing>;

    struct line_height {
    using property_value_type = uint16_t;

    static constexpr auto property_name_rule = LEXY_LIT("line-height");
    static constexpr auto property_value_rule = dsl::integer<property_value_type>;
    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_line_height = as_property<line_height>;

    struct wrap_mode {
    using property_value_type = Clay_TextElementConfigWrapMode;

    struct words_value {
    static constexpr auto rule = LEXY_LIT("words");
    static constexpr auto value = lexy::constant(CLAY_TEXT_WRAP_WORDS);
    };

    struct newlines_value {
    static constexpr auto rule = LEXY_LIT("newlines");
    static constexpr auto value = lexy::constant(CLAY_TEXT_WRAP_NEWLINES);
    };

    struct none_value {
    static constexpr auto rule = LEXY_LIT("none");
    static constexpr auto value = lexy::constant(CLAY_TEXT_WRAP_NONE);
    };

    static constexpr auto property_name_rule = LEXY_LIT("wrap-mode");
    static constexpr auto property_value_rule
    = dsl::p<words_value>
    | dsl::p<newlines_value>
    | dsl::p<none_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_wrap_mode = as_property<wrap_mode>;

    struct alignment {
    using property_value_type = Clay_TextAlignment;

    struct left_value {
    static constexpr auto rule = LEXY_LIT("left");
    static constexpr auto value = lexy::constant(CLAY_TEXT_ALIGN_LEFT);
    };

    struct center_value {
    static constexpr auto rule = LEXY_LIT("center");
    static constexpr auto value = lexy::constant(CLAY_TEXT_ALIGN_CENTER);
    };

    struct right_value {
    static constexpr auto rule = LEXY_LIT("right");
    static constexpr auto value = lexy::constant(CLAY_TEXT_ALIGN_RIGHT);
    };

    static constexpr auto property_name_rule = LEXY_LIT("alignment");
    static constexpr auto property_value_rule
    = dsl::p<left_value>
    | dsl::p<center_value>
    | dsl::p<right_value>
    ;

    static constexpr auto property_value_eval = lexy::forward<property_value_type>;
    };
    using prop_alignment = as_property<alignment>;

    static constexpr auto rule = dsl::partial_combination(
    dsl::p<prop_color>,
    dsl::p<prop_font_face>,
    dsl::p<prop_font_size>,
    dsl::p<prop_letter_spacing>,
    dsl::p<prop_line_height>,
    dsl::p<prop_wrap_mode>,
    dsl::p<prop_alignment>
    );


    using property_setter = std::function<void(manager&, text_style&)>;
    using property_setters = std::vector<property_setter>;

    static constexpr auto value = lexy::fold_inplace<property_setters>(
    property_setters{},
    [](property_setters& setters, prop_color::property_value_type property) {
    setters.push_back([property](manager&, text_style& config) {
    auto [_, value] = property;
    config.textColor = value;
    });
    },
    [](property_setters& setters, prop_font_face::property_value_type property) {
    setters.push_back([property](manager& manager, text_style& config) {
    auto [_, value] = property;
    auto font_id = manager.add_font_to_atlas(value);
    config.fontId = font_id;
    });
    },
    [](property_setters& setters, prop_font_size::property_value_type property) {
    setters.push_back([property](manager&, text_style& config) {
    auto [_, value] = property;
    config.fontSize = value;
    });
    },
    [](property_setters& setters, prop_letter_spacing::property_value_type property) {
    setters.push_back([property](manager&, text_style& config) {
    auto [_, value] = property;
    config.letterSpacing = value;
    });
    },
    [](property_setters& setters, prop_line_height::property_value_type property) {
    setters.push_back([property](manager&, text_style& config) {
    auto [_, value] = property;
    config.lineHeight = value;
    });
    },
    [](property_setters& setters, prop_wrap_mode::property_value_type property) {
    setters.push_back([property](manager&, text_style& config) {
    auto [_, value] = property;
    config.wrapMode = value;
    });
    },
    [](property_setters& setters, prop_alignment::property_value_type property) {
    setters.push_back([property](manager&, text_style& config) {
    auto [_, value] = property;
    config.textAlignment = value;
    });
    }
    ) >> lexy::forward<property_setters>;
    };

    // MARK: elt props state
    struct container_properties_with_state {
    static constexpr auto rule
    = dsl::p<property_set::element_properties>
    + dsl::opt(dsl::lit_c<'&'> >> dsl::lit_c<':'> >> LEXY_LIT("hover") >> dsl::curly_bracketed(dsl::p<property_set::element_properties>))
    + dsl::opt(dsl::lit_c<'&'> >> dsl::lit_c<':'> >> LEXY_LIT("active") >> dsl::curly_bracketed(dsl::p<property_set::element_properties>))
    ;

    static constexpr auto value = lexy::callback_with_state<container_style_with_state>(
    [](
    manager& ui_manager,
    property_set::element_properties::property_setters normal_setters,
    lexy::nullopt,
    lexy::nullopt
    ) {
    auto style = container_style_with_state{};

    for (auto& setter : normal_setters) {
    setter(ui_manager, style.normal);
    }

    style.hovered = style.normal;
    style.active = style.normal;

    return style;
    },
    [](
    manager& ui_manager,
    property_set::element_properties::property_setters normal_setters,
    property_set::element_properties::property_setters hovered_setters,
    lexy::nullopt
    ) {
    auto style = container_style_with_state{};

    for (auto& setter : normal_setters) {
    setter(ui_manager, style.normal);
    }

    style.hovered = style.normal;
    style.active = style.normal;

    for (auto& setter : hovered_setters) {
    setter(ui_manager, style.hovered);
    }

    return style;
    },

    [](
    manager& ui_manager,
    property_set::element_properties::property_setters normal_setters,
    lexy::nullopt,
    property_set::element_properties::property_setters active_setters
    ) {
    auto style = container_style_with_state{};

    for (auto& setter : normal_setters) {
    setter(ui_manager, style.normal);
    }

    style.hovered = style.normal;
    style.active = style.normal;

    for (auto& setter : active_setters) {
    setter(ui_manager, style.active);
    }

    return style;
    },
    [](
    manager& ui_manager,
    property_set::element_properties::property_setters normal_setters,
    property_set::element_properties::property_setters hovered_setters,
    property_set::element_properties::property_setters active_setters
    ) {
    auto style = container_style_with_state{};

    for (auto& setter : normal_setters) {
    setter(ui_manager, style.normal);
    }

    style.hovered = style.normal;
    style.active = style.normal;

    for (auto& setter : hovered_setters) {
    setter(ui_manager, style.hovered);
    }

    for (auto& setter : active_setters) {
    setter(ui_manager, style.active);
    }

    return style;
    }
    );
    };

    // MARK: txt props state
    struct text_properties_with_state {
    static constexpr auto rule
    = dsl::p<property_set::text_properties>
    + dsl::opt(dsl::lit_c<'&'> >> dsl::lit_c<':'> >> LEXY_LIT("hover") >> dsl::curly_bracketed(dsl::p<property_set::text_properties>))
    + dsl::opt(dsl::lit_c<'&'> >> dsl::lit_c<':'> >> LEXY_LIT("active") >> dsl::curly_bracketed(dsl::p<property_set::text_properties>))
    ;

    static constexpr auto value = lexy::callback_with_state<text_style_with_state>(
    [](
    manager& ui_manager,
    property_set::text_properties::property_setters normal_setters,
    std::optional<property_set::text_properties::property_setters> hovered_setters,
    std::optional<property_set::text_properties::property_setters> active_setters
    ) {
    auto style = text_style_with_state{};

    for (auto& setter : normal_setters) {
    setter(ui_manager, style.normal);
    }

    style.hovered = style.normal;
    style.active = style.normal;

    if (hovered_setters.has_value()) {
    for (auto& setter : hovered_setters.value()) {
    setter(ui_manager, style.hovered);
    }
    }

    if (active_setters.has_value()) {
    for (auto& setter : active_setters.value()) {
    setter(ui_manager, style.active);
    }
    }

    return style;
    }
    );
    };
    }

    namespace block {
    // MARK: elt block
    struct element_block {
    using block_type = std::pair<std::string, element_style>;

    static constexpr auto rule
    = LEXY_LIT("element")
    >> dsl::hash_sign
    + dsl::p<basic::identifier>
    + dsl::curly_bracketed(dsl::p<property_set::element_properties>)
    ;

    static constexpr auto value = lexy::callback_with_state<block_type>(
    [](manager& ui_manager, std::string id, property_set::element_properties::property_setters setters) {
    auto style = element_style{};

    for (auto& setter : setters) {
    setter(ui_manager, style);
    }

    return block_type{id, style};
    }
    );
    };

    // MARK: txt block
    struct text_block {
    using block_type = std::pair<std::string, text_style>;

    static constexpr auto rule
    = LEXY_LIT("text")
    >> dsl::hash_sign
    + dsl::p<basic::identifier>
    + dsl::curly_bracketed(dsl::p<property_set::text_properties>)
    ;

    static constexpr auto value = lexy::callback_with_state<block_type>(
    [](manager& ui_manager, std::string id, property_set::text_properties::property_setters setters) {
    auto style = text_style{};

    for (auto& setter : setters) {
    setter(ui_manager, style);
    }

    return block_type{id, style};
    }
    );
    };

    // MARK: button block
    struct button_block {
    using block_type = std::pair<std::string, button_style>;

    struct container_subblock {
    static constexpr auto rule
    = dsl::lit_c<'&'>
    >> dsl::lit_c<'.'>
    >> LEXY_LIT("container")
    >> dsl::curly_bracketed(dsl::p<property_set::container_properties_with_state>)
    ;

    static constexpr auto value = lexy::forward<container_style_with_state>;
    };

    struct label_subblock {
    static constexpr auto rule
    = dsl::lit_c<'&'>
    >> dsl::lit_c<'.'>
    >> LEXY_LIT("label")
    >> dsl::curly_bracketed(dsl::p<property_set::text_properties_with_state>)
    ;

    static constexpr auto value = lexy::forward<text_style_with_state>;
    };

    static constexpr auto rule
    = LEXY_LIT("button")
    >> dsl::hash_sign
    + dsl::p<basic::identifier>
    + dsl::curly_bracketed(dsl::p<container_subblock> + dsl::p<label_subblock>)
    ;

    static constexpr auto value = lexy::callback<block_type>(
    [](
    std::string id,
    container_style_with_state container,
    text_style_with_state label
    ) {
    return block_type{id, button_style{container, label}};
    }
    );
    };

    // MARK: input block
    struct input_block {
    using block_type = std::pair<std::string, input_style>;

    struct container_subblock {
    static constexpr auto rule
    = dsl::lit_c<'&'>
    >> dsl::lit_c<'.'>
    >> LEXY_LIT("container")
    >> dsl::curly_bracketed(dsl::p<property_set::container_properties_with_state>)
    ;

    static constexpr auto value = lexy::forward<container_style_with_state>;
    };

    struct content_subblock {
    static constexpr auto rule
    = dsl::lit_c<'&'>
    >> dsl::lit_c<'.'>
    >> LEXY_LIT("content")
    >> dsl::curly_bracketed(dsl::p<property_set::text_properties_with_state>)
    ;

    static constexpr auto value = lexy::forward<text_style_with_state>;
    };

    static constexpr auto rule
    = LEXY_LIT("input")
    >> dsl::hash_sign
    + dsl::p<basic::identifier>
    + dsl::curly_bracketed(dsl::p<container_subblock> + dsl::p<content_subblock>)
    ;

    static constexpr auto value = lexy::callback<block_type>(
    [](
    std::string id,
    container_style_with_state container,
    text_style_with_state content
    ) {
    return block_type{id, input_style{container, content}};
    }
    );
    };
    }

    // MARK: document
    struct document {
    using block_type = std::variant<
    block::element_block::block_type,
    block::text_block::block_type,
    block::button_block::block_type,
    block::input_block::block_type
    >;

    static constexpr auto whitespace = dsl::ascii::space;

    static constexpr auto rule =
    dsl::list(
    dsl::p<block::element_block>
    | dsl::p<block::text_block>
    | dsl::p<block::button_block>
    | dsl::p<block::input_block>
    );

    static constexpr auto value
    = lexy::as_list<std::vector<block_type>>
    >> lexy::callback<stylesheet>(
    [](const std::vector<block_type>& blocks) {
    auto sheet = stylesheet{};

    for (auto& block : blocks) {
    if (std::holds_alternative<block::element_block::block_type>(block)) {
    auto [id, style] = std::get<block::element_block::block_type>(block);
    sheet.elements[id] = style;
    }
    else if (std::holds_alternative<block::text_block::block_type>(block)) {
    auto [id, style] = std::get<block::text_block::block_type>(block);
    sheet.texts[id] = style;
    }
    else if (std::holds_alternative<block::button_block::block_type>(block)) {
    auto [id, style] = std::get<block::button_block::block_type>(block);
    sheet.buttons[id] = style;
    }
    else if (std::holds_alternative<block::input_block::block_type>(block)) {
    auto [id, style] = std::get<block::input_block::block_type>(block);
    sheet.inputs[id] = style;
    }
    }

    return sheet;
    }
    );
    };


    stylesheet eval(manager& ui_manager, const std::string& source);
    }
    41 changes: 41 additions & 0 deletions types.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    #pragma once

    #include <string>
    #include <unordered_map>

    #include <clay.h>


    namespace engine::ui {
    using element_style = Clay_ElementDeclaration;
    using text_style = Clay_TextElementConfig;

    struct container_style_with_state {
    element_style normal;
    element_style hovered;
    element_style active;
    };

    struct text_style_with_state {
    text_style normal;
    text_style hovered;
    text_style active;
    };

    struct button_style {
    container_style_with_state container;
    text_style_with_state label;
    };

    struct input_style {
    container_style_with_state container;
    text_style_with_state content;
    };

    struct stylesheet {
    std::unordered_map<std::string, element_style> elements;
    std::unordered_map<std::string, text_style> texts;
    std::unordered_map<std::string, button_style> buttons;
    std::unordered_map<std::string, input_style> inputs;
    };
    }