From d1673c3eef5122a3c604654a8e61bb1424f9496f Mon Sep 17 00:00:00 2001 From: echo Date: Fri, 22 May 2026 17:42:51 +0200 Subject: [PATCH] Phase E+F: Move primitives to primitives.odin, split theme constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- odin/src/gui/clay_layout.odin | 81 --------------------------- odin/src/gui/clay_theme.odin | 89 ++++++++++++++++++++++++++++++ odin/src/gui/primitives.odin | 101 ++++++++++++++++++++++++++++++++++ odin/src/gui/runtime.odin | 94 ------------------------------- 4 files changed, 190 insertions(+), 175 deletions(-) create mode 100644 odin/src/gui/clay_theme.odin create mode 100644 odin/src/gui/primitives.odin diff --git a/odin/src/gui/clay_layout.odin b/odin/src/gui/clay_layout.odin index 74b6263..460fced 100644 --- a/odin/src/gui/clay_layout.odin +++ b/odin/src/gui/clay_layout.odin @@ -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 { diff --git a/odin/src/gui/clay_theme.odin b/odin/src/gui/clay_theme.odin new file mode 100644 index 0000000..c1b7a32 --- /dev/null +++ b/odin/src/gui/clay_theme.odin @@ -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) diff --git a/odin/src/gui/primitives.odin b/odin/src/gui/primitives.odin new file mode 100644 index 0000000..07c5afd --- /dev/null +++ b/odin/src/gui/primitives.odin @@ -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) + } +} + diff --git a/odin/src/gui/runtime.odin b/odin/src/gui/runtime.odin index 88df214..8a99719 100644 --- a/odin/src/gui/runtime.odin +++ b/odin/src/gui/runtime.odin @@ -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) - } -} -