Phase E+F: Move primitives to primitives.odin, split theme constants

Phase E: primitives.odin (101 lines)
  declare_nav_chip, declare_button* (all 6 variants),
  declare_status_badge, declare_stat_chip

Phase F: clay_theme.odin (89 lines)
  All CLAY_* color/spacing/font/radius constants extracted
  clay_layout.odin retains only functions and state

runtime.odin: 812 → 718 lines
All 156 tests pass.
This commit is contained in:
echo 2026-05-22 17:42:51 +02:00
parent 8b044e3ac1
commit d1673c3eef
4 changed files with 190 additions and 175 deletions

View File

@ -20,87 +20,6 @@ Raylib_Font :: struct {
raylib_fonts: [dynamic]Raylib_Font
// --- Clay Color Palette (modernized dark theme) ---
// Named with CLAY_ prefix to avoid collision with existing theme.odin
CLAY_BG_BASE :: clay.Color{13, 13, 18, 255}
CLAY_BG_SIDEBAR :: clay.Color{18, 18, 26, 255}
CLAY_BG_TOPBAR :: clay.Color{22, 22, 32, 255}
CLAY_BG_CARD :: clay.Color{28, 28, 40, 255}
CLAY_BG_CARD_ALT :: clay.Color{24, 24, 36, 255}
CLAY_BG_STRIP :: clay.Color{32, 32, 46, 255}
CLAY_BG_OVERLAY :: clay.Color{0, 0, 0, 180}
CLAY_BG_INPUT :: clay.Color{20, 20, 30, 255}
CLAY_BORDER_CARD :: clay.Color{50, 50, 70, 255}
CLAY_BORDER_SUBTLE :: clay.Color{40, 40, 58, 255}
CLAY_BORDER_DIVIDER :: clay.Color{36, 36, 52, 255}
CLAY_ACCENT :: clay.Color{99, 102, 241, 255}
CLAY_ACCENT_HOVER :: clay.Color{124, 127, 255, 255}
CLAY_ACCENT_MUTED :: clay.Color{79, 70, 229, 200}
CLAY_ACCENT_SURFACE:: clay.Color{67, 56, 202, 40}
CLAY_ACCENT_GLOW :: clay.Color{99, 102, 241, 60}
CLAY_TEXT_PRIMARY :: clay.Color{245, 245, 250, 255}
CLAY_TEXT_SECONDARY :: clay.Color{190, 190, 210, 255}
CLAY_TEXT_TERTIARY :: clay.Color{145, 145, 170, 255}
CLAY_TEXT_DISABLED :: clay.Color{100, 100, 120, 255}
CLAY_TEXT_BRIGHT := clay.Color{255, 255, 255, 255}
CLAY_SUCCESS :: clay.Color{34, 197, 94, 255}
CLAY_SUCCESS_DIM :: clay.Color{34, 197, 94, 100}
CLAY_WARNING :: clay.Color{234, 179, 8, 255}
CLAY_WARNING_DIM :: clay.Color{234, 179, 8, 100}
CLAY_ERROR :: clay.Color{239, 68, 68, 255}
CLAY_ERROR_DIM :: clay.Color{239, 68, 68, 100}
CLAY_BTN_DEFAULT :: clay.Color{42, 42, 58, 255}
CLAY_BTN_DEFAULT_HOVER :: clay.Color{55, 55, 72, 255}
CLAY_BTN_SOFT :: clay.Color{55, 50, 120, 255}
CLAY_BTN_SOFT_HOVER :: clay.Color{70, 65, 140, 255}
CLAY_BTN_DANGER :: clay.Color{185, 28, 28, 255}
CLAY_BTN_DANGER_HOVER :: clay.Color{210, 40, 40, 255}
CLAY_BTN_DISABLED :: clay.Color{30, 30, 42, 255}
CLAY_NAV_ACTIVE :: clay.Color{99, 102, 241, 255}
CLAY_NAV_ACTIVE_BG :: clay.Color{67, 56, 202, 25}
CLAY_NAV_HOVER_BG :: clay.Color{40, 40, 58, 255}
CLAY_INPUT_BORDER :: clay.Color{55, 55, 75, 255}
CLAY_INPUT_FOCUS :: clay.Color{99, 102, 241, 255}
CLAY_PROGRESS_TRACK :: clay.Color{30, 30, 42, 255}
CLAY_PROGRESS_FILL := clay.Color{99, 102, 241, 255}
// --- Spacing (4px grid) ---
CLAY_SPACE_4 :: u16(4)
CLAY_SPACE_8 :: u16(8)
CLAY_SPACE_12 :: u16(12)
CLAY_SPACE_16 :: u16(16)
CLAY_SPACE_20 :: u16(20)
CLAY_SPACE_24 :: u16(24)
CLAY_SPACE_32 :: u16(32)
CLAY_SPACE_40 :: u16(40)
CLAY_SPACE_48 :: u16(48)
// --- Font Sizes ---
CLAY_FONT_SIZE_SM :: u16(15)
CLAY_FONT_SIZE_MD :: u16(17)
CLAY_FONT_SIZE_LG :: u16(21)
CLAY_FONT_SIZE_XL :: u16(26)
CLAY_FONT_SIZE_2XL:: u16(32)
// --- Corner Radius (px for Clay) ---
CLAY_RADIUS_SM :: f32(4)
CLAY_RADIUS_MD :: f32(8)
CLAY_RADIUS_LG :: f32(12)
CLAY_RADIUS_XL :: f32(16)
// --- Fractional radius (for DrawRectangleRounded compat) ---
CLAY_RADIUS_FRAC_BTN :: f32(0.32)
CLAY_RADIUS_FRAC_PILL :: f32(0.50)
CLAY_RADIUS_FRAC_CARD :: f32(0.14)
// --- Color conversion ---
clay_color_to_rl :: proc(color: clay.Color) -> rl.Color {

View File

@ -0,0 +1,89 @@
package gui
import clay "clay:."
// --- Color Palette ---
// --- Clay Color Palette (modernized dark theme) ---
// Named with CLAY_ prefix to avoid collision with existing theme.odin
CLAY_BG_BASE :: clay.Color{13, 13, 18, 255}
CLAY_BG_SIDEBAR :: clay.Color{18, 18, 26, 255}
CLAY_BG_TOPBAR :: clay.Color{22, 22, 32, 255}
CLAY_BG_CARD :: clay.Color{28, 28, 40, 255}
CLAY_BG_CARD_ALT :: clay.Color{24, 24, 36, 255}
CLAY_BG_STRIP :: clay.Color{32, 32, 46, 255}
CLAY_BG_OVERLAY :: clay.Color{0, 0, 0, 180}
CLAY_BG_INPUT :: clay.Color{20, 20, 30, 255}
CLAY_BORDER_CARD :: clay.Color{50, 50, 70, 255}
CLAY_BORDER_SUBTLE :: clay.Color{40, 40, 58, 255}
CLAY_BORDER_DIVIDER :: clay.Color{36, 36, 52, 255}
CLAY_ACCENT :: clay.Color{99, 102, 241, 255}
CLAY_ACCENT_HOVER :: clay.Color{124, 127, 255, 255}
CLAY_ACCENT_MUTED :: clay.Color{79, 70, 229, 200}
CLAY_ACCENT_SURFACE:: clay.Color{67, 56, 202, 40}
CLAY_ACCENT_GLOW :: clay.Color{99, 102, 241, 60}
CLAY_TEXT_PRIMARY :: clay.Color{245, 245, 250, 255}
CLAY_TEXT_SECONDARY :: clay.Color{190, 190, 210, 255}
CLAY_TEXT_TERTIARY :: clay.Color{145, 145, 170, 255}
CLAY_TEXT_DISABLED :: clay.Color{100, 100, 120, 255}
CLAY_TEXT_BRIGHT := clay.Color{255, 255, 255, 255}
CLAY_SUCCESS :: clay.Color{34, 197, 94, 255}
CLAY_SUCCESS_DIM :: clay.Color{34, 197, 94, 100}
CLAY_WARNING :: clay.Color{234, 179, 8, 255}
CLAY_WARNING_DIM :: clay.Color{234, 179, 8, 100}
CLAY_ERROR :: clay.Color{239, 68, 68, 255}
CLAY_ERROR_DIM :: clay.Color{239, 68, 68, 100}
CLAY_BTN_DEFAULT :: clay.Color{42, 42, 58, 255}
CLAY_BTN_DEFAULT_HOVER :: clay.Color{55, 55, 72, 255}
CLAY_BTN_SOFT :: clay.Color{55, 50, 120, 255}
CLAY_BTN_SOFT_HOVER :: clay.Color{70, 65, 140, 255}
CLAY_BTN_DANGER :: clay.Color{185, 28, 28, 255}
CLAY_BTN_DANGER_HOVER :: clay.Color{210, 40, 40, 255}
CLAY_BTN_DISABLED :: clay.Color{30, 30, 42, 255}
CLAY_NAV_ACTIVE :: clay.Color{99, 102, 241, 255}
CLAY_NAV_ACTIVE_BG :: clay.Color{67, 56, 202, 25}
CLAY_NAV_HOVER_BG :: clay.Color{40, 40, 58, 255}
CLAY_INPUT_BORDER :: clay.Color{55, 55, 75, 255}
CLAY_INPUT_FOCUS :: clay.Color{99, 102, 241, 255}
CLAY_PROGRESS_TRACK :: clay.Color{30, 30, 42, 255}
CLAY_PROGRESS_FILL := clay.Color{99, 102, 241, 255}
// --- Spacing (4px grid) ---
// --- Spacing (4px grid) ---
CLAY_SPACE_4 :: u16(4)
CLAY_SPACE_8 :: u16(8)
CLAY_SPACE_12 :: u16(12)
CLAY_SPACE_16 :: u16(16)
CLAY_SPACE_20 :: u16(20)
CLAY_SPACE_24 :: u16(24)
CLAY_SPACE_32 :: u16(32)
CLAY_SPACE_40 :: u16(40)
CLAY_SPACE_48 :: u16(48)
// --- Font Sizes ---
// --- Font Sizes ---
CLAY_FONT_SIZE_SM :: u16(15)
CLAY_FONT_SIZE_MD :: u16(17)
CLAY_FONT_SIZE_LG :: u16(21)
CLAY_FONT_SIZE_XL :: u16(26)
CLAY_FONT_SIZE_2XL:: u16(32)
// --- Corner Radius ---
// --- Corner Radius (px for Clay) ---
CLAY_RADIUS_SM :: f32(4)
CLAY_RADIUS_MD :: f32(8)
CLAY_RADIUS_LG :: f32(12)
CLAY_RADIUS_XL :: f32(16)
// --- Fractional radius (for DrawRectangleRounded compat) ---
CLAY_RADIUS_FRAC_BTN :: f32(0.32)
CLAY_RADIUS_FRAC_PILL :: f32(0.50)
CLAY_RADIUS_FRAC_CARD :: f32(0.14)

View File

@ -0,0 +1,101 @@
package gui
import clay "clay:."
import "core:fmt"
// Clay UI Primitives
declare_nav_chip :: proc(id: string, label: string, active: bool) {
bg: clay.Color = clay.Color{0, 0, 0, 0}
if active { bg = CLAY_NAV_HOVER_BG }
if clay.UI(clay.ID(id))({
layout = {sizing = {width = clay.SizingFit({min = 70, max = 34}), height = clay.SizingFixed(34)}, padding = {top = 4, right = 12, bottom = 4, left = 12}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = bg,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_SM),
}) {
text_color: clay.Color = CLAY_TEXT_SECONDARY
if active { text_color = CLAY_TEXT_BRIGHT }
clay_body_text(label, color = text_color)
}
}
declare_button :: proc(id: string, label: string, bg, hover_bg: clay.Color) {
is_hovered := clay.Hovered()
current_bg: clay.Color = bg
if is_hovered { current_bg = hover_bg }
if clay.UI(clay.ID(id))({
layout = clay_button_layout(),
backgroundColor = current_bg,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_FRAC_BTN),
}) {
clay_body_text(label, color = CLAY_TEXT_PRIMARY)
}
}
declare_button_default :: proc(id: string, label: string) {
declare_button(id, label, CLAY_BTN_DEFAULT, CLAY_BTN_DEFAULT_HOVER)
}
declare_button_danger :: proc(id: string, label: string) {
declare_button(id, label, CLAY_BTN_DANGER, CLAY_BTN_DANGER_HOVER)
}
declare_button_soft :: proc(id: string, label: string) {
declare_button(id, label, CLAY_BTN_SOFT, CLAY_BTN_SOFT_HOVER)
}
declare_button_primary :: proc(id: string, label: string) {
declare_button(id, label, CLAY_ACCENT, CLAY_ACCENT_HOVER)
}
declare_button_small :: proc(id: string, label: string) {
if clay.UI(clay.ID(id))({
layout = {sizing = {width = clay.SizingFit({min = 40, max = 24}), height = clay.SizingFixed(24)}, padding = {top = 2, right = 8, bottom = 2, left = 8}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = CLAY_BTN_DEFAULT,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_SM),
}) {
clay.Text(label, {fontId = CLAY_FONT_BODY, fontSize = CLAY_FONT_SIZE_SM, textColor = CLAY_TEXT_SECONDARY})
}
}
declare_button_recommended :: proc(id: string, label: string) {
if clay.UI(clay.ID(id))({
layout = clay_button_layout(),
backgroundColor = CLAY_ACCENT,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_FRAC_BTN),
border = {color = CLAY_ACCENT_GLOW, width = clay.BorderOutside(2)},
}) {
clay_body_text(label, color = CLAY_TEXT_BRIGHT)
}
}
declare_status_badge :: proc(id: string, label: string, ok: bool) {
badge_color: clay.Color = CLAY_ERROR
if ok { badge_color = CLAY_SUCCESS }
badge_bg: clay.Color = CLAY_ERROR_DIM
if ok { badge_bg = CLAY_SUCCESS_DIM }
if clay.UI(clay.ID(id))({
layout = {sizing = {width = clay.SizingFit({min = 70, max = 28}), height = clay.SizingFixed(26)}, padding = {top = 2, right = 8, bottom = 2, left = 8}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = badge_bg,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_MD),
border = {color = badge_color, width = clay.BorderOutside(1)},
}) {
clay.Text(label, {fontId = CLAY_FONT_BODY, fontSize = CLAY_FONT_SIZE_SM, textColor = badge_color})
}
}
// Stat Chip (Clay)
// Stat Chip (Clay)
declare_stat_chip :: proc(label: string, value: int) {
value_text := fmt.tprintf("%d", value)
if clay.UI(clay.ID("StatChip", u32(label[0])))({
layout = {sizing = {width = clay.SizingFixed(90), height = clay.SizingFixed(34)}, padding = {top = 4, right = 8, bottom = 4, left = 8}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = clay.Color{40, 40, 55, 255},
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_SM),
border = {color = clay.Color{55, 55, 75, 255}, width = clay.BorderOutside(1)},
}) {
clay_muted_text(label)
clay_body_text(value_text, color = CLAY_TEXT_PRIMARY, size = CLAY_FONT_SIZE_SM)
}
}

View File

@ -612,86 +612,6 @@ process_clicks :: proc(app: ^GUI_App_State, can_gen_panels, can_layout, can_expo
}
}
}
// Clay UI Primitives
declare_nav_chip :: proc(id: string, label: string, active: bool) {
bg: clay.Color = clay.Color{0, 0, 0, 0}
if active { bg = CLAY_NAV_HOVER_BG }
if clay.UI(clay.ID(id))({
layout = {sizing = {width = clay.SizingFit({min = 70, max = 34}), height = clay.SizingFixed(34)}, padding = {top = 4, right = 12, bottom = 4, left = 12}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = bg,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_SM),
}) {
text_color: clay.Color = CLAY_TEXT_SECONDARY
if active { text_color = CLAY_TEXT_BRIGHT }
clay_body_text(label, color = text_color)
}
}
declare_button :: proc(id: string, label: string, bg, hover_bg: clay.Color) {
is_hovered := clay.Hovered()
current_bg: clay.Color = bg
if is_hovered { current_bg = hover_bg }
if clay.UI(clay.ID(id))({
layout = clay_button_layout(),
backgroundColor = current_bg,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_FRAC_BTN),
}) {
clay_body_text(label, color = CLAY_TEXT_PRIMARY)
}
}
declare_button_default :: proc(id: string, label: string) {
declare_button(id, label, CLAY_BTN_DEFAULT, CLAY_BTN_DEFAULT_HOVER)
}
declare_button_danger :: proc(id: string, label: string) {
declare_button(id, label, CLAY_BTN_DANGER, CLAY_BTN_DANGER_HOVER)
}
declare_button_soft :: proc(id: string, label: string) {
declare_button(id, label, CLAY_BTN_SOFT, CLAY_BTN_SOFT_HOVER)
}
declare_button_primary :: proc(id: string, label: string) {
declare_button(id, label, CLAY_ACCENT, CLAY_ACCENT_HOVER)
}
declare_button_small :: proc(id: string, label: string) {
if clay.UI(clay.ID(id))({
layout = {sizing = {width = clay.SizingFit({min = 40, max = 24}), height = clay.SizingFixed(24)}, padding = {top = 2, right = 8, bottom = 2, left = 8}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = CLAY_BTN_DEFAULT,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_SM),
}) {
clay.Text(label, {fontId = CLAY_FONT_BODY, fontSize = CLAY_FONT_SIZE_SM, textColor = CLAY_TEXT_SECONDARY})
}
}
declare_button_recommended :: proc(id: string, label: string) {
if clay.UI(clay.ID(id))({
layout = clay_button_layout(),
backgroundColor = CLAY_ACCENT,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_FRAC_BTN),
border = {color = CLAY_ACCENT_GLOW, width = clay.BorderOutside(2)},
}) {
clay_body_text(label, color = CLAY_TEXT_BRIGHT)
}
}
declare_status_badge :: proc(id: string, label: string, ok: bool) {
badge_color: clay.Color = CLAY_ERROR
if ok { badge_color = CLAY_SUCCESS }
badge_bg: clay.Color = CLAY_ERROR_DIM
if ok { badge_bg = CLAY_SUCCESS_DIM }
if clay.UI(clay.ID(id))({
layout = {sizing = {width = clay.SizingFit({min = 70, max = 28}), height = clay.SizingFixed(26)}, padding = {top = 2, right = 8, bottom = 2, left = 8}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = badge_bg,
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_MD),
border = {color = badge_color, width = clay.BorderOutside(1)},
}) {
clay.Text(label, {fontId = CLAY_FONT_BODY, fontSize = CLAY_FONT_SIZE_SM, textColor = badge_color})
}
}
// Help Overlay (Clay floating)
declare_help_overlay :: proc() {
// Backdrop
@ -796,17 +716,3 @@ declare_toast :: proc(log: ^Action_Log) {
}
}
// Stat Chip (Clay)
declare_stat_chip :: proc(label: string, value: int) {
value_text := fmt.tprintf("%d", value)
if clay.UI(clay.ID("StatChip", u32(label[0])))({
layout = {sizing = {width = clay.SizingFixed(90), height = clay.SizingFixed(34)}, padding = {top = 4, right = 8, bottom = 4, left = 8}, childAlignment = {x = .Center, y = .Center}},
backgroundColor = clay.Color{40, 40, 55, 255},
cornerRadius = clay.CornerRadiusAll(CLAY_RADIUS_SM),
border = {color = clay.Color{55, 55, 75, 255}, width = clay.BorderOutside(1)},
}) {
clay_muted_text(label)
clay_body_text(value_text, color = CLAY_TEXT_PRIMARY, size = CLAY_FONT_SIZE_SM)
}
}