Skip to main content

khora_core/ui/
theme.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Backend-agnostic UI theme — palette + sizing + type tokens.
16//!
17//! All slots are *semantic*: `primary`, `accent_a`, `success`, etc. The theme
18//! contains no brand-specific naming — apps configure the concrete values.
19//! Color slots are `[r, g, b, a]` **sRGB** components in `0.0..=1.0` (see the
20//! note on the color pipeline in [`crate::ui::brand`]); the concrete UI
21//! backend feeds them to its native format.
22//!
23//! The same struct is used by both the editor (`khora-editor`) and the hub
24//! (`hub`). Both install the one canonical [`crate::ui::brand::khora_dark`]
25//! producer, so there is a single source of truth and the two apps cannot
26//! drift. [`Default`] stays a neutral fallback for anything that wants a theme
27//! without pulling in the brand.
28
29/// Color palette and sizing tokens for any Khora UI surface.
30#[derive(Debug, Clone)]
31pub struct UiTheme {
32    // ── Surfaces ─────────────────────────────────────
33    /// Outermost background (empty regions, behind everything).
34    pub background: [f32; 4],
35    /// Default panel surface.
36    pub surface: [f32; 4],
37    /// Slightly elevated surface (e.g. tab bar, card body).
38    pub surface_elevated: [f32; 4],
39    /// Interactive surface (buttons, combo boxes).
40    pub surface_interactive: [f32; 4],
41    /// Hovered or active surface highlight.
42    pub surface_active: [f32; 4],
43
44    // ── Lines / borders ──────────────────────────────
45    /// Subtle separator (between items inside a panel).
46    pub separator: [f32; 4],
47    /// Default panel border.
48    pub border: [f32; 4],
49    /// Stronger border (used for emphasis around modals, popups).
50    pub border_strong: [f32; 4],
51
52    // ── Text ─────────────────────────────────────────
53    /// Primary text color.
54    pub text: [f32; 4],
55    /// Secondary text (labels, sub-titles).
56    pub text_dim: [f32; 4],
57    /// Tertiary text (hints, captions).
58    pub text_muted: [f32; 4],
59    /// Disabled text.
60    pub text_disabled: [f32; 4],
61    /// Dark "inverse" ink for text sitting on a filled brand (`primary`)
62    /// surface — e.g. the label of a solid silver primary button.
63    pub text_inverse: [f32; 4],
64
65    // ── Brand / accents ──────────────────────────────
66    /// Primary brand color (selection rings, focus highlights).
67    pub primary: [f32; 4],
68    /// Dimmed primary (backgrounds of selected rows, etc.).
69    pub primary_dim: [f32; 4],
70    /// Accent A — secondary brand color.
71    pub accent_a: [f32; 4],
72    /// Accent B — tertiary brand color.
73    pub accent_b: [f32; 4],
74    /// Accent C — quaternary brand color (warnings of-emphasis, special states).
75    pub accent_c: [f32; 4],
76
77    // ── Status colors ────────────────────────────────
78    /// Success indicator (green-family).
79    pub success: [f32; 4],
80    /// Warning indicator (amber-family).
81    pub warning: [f32; 4],
82    /// Error indicator (red-family).
83    pub error: [f32; 4],
84
85    // ── 3D axes ──────────────────────────────────────
86    /// Color for the X axis (typically red-orange).
87    pub axis_x: [f32; 4],
88    /// Color for the Y axis (typically green).
89    pub axis_y: [f32; 4],
90    /// Color for the Z axis (typically blue).
91    pub axis_z: [f32; 4],
92
93    // ── Sizing tokens ────────────────────────────────
94    /// Small corner radius (chips, badges).
95    pub radius_sm: f32,
96    /// Medium corner radius (default for buttons / inputs).
97    pub radius_md: f32,
98    /// Large corner radius (panels, cards).
99    pub radius_lg: f32,
100    /// Extra-large corner radius (modal dialogs, palettes).
101    pub radius_xl: f32,
102
103    // ── Type sizes ───────────────────────────────────
104    /// Caption / metadata font size in logical points.
105    pub font_size_caption: f32,
106    /// Body / default font size.
107    pub font_size_body: f32,
108    /// Section title font size.
109    pub font_size_title: f32,
110    /// Display heading font size.
111    pub font_size_display: f32,
112
113    // ── Spacing ──────────────────────────────────────
114    /// Default vertical padding for a row of content.
115    pub pad_row: f32,
116    /// Default inner padding for a card.
117    pub pad_card: f32,
118}
119
120/// Default theme: a neutral dark palette suitable for any app.
121///
122/// Apps that want a branded look (e.g. the Khora "Deep Navy / Silver" palette)
123/// build their own [`UiTheme`] and pass it where needed.
124impl Default for UiTheme {
125    fn default() -> Self {
126        Self {
127            // Surfaces
128            background: [0.039, 0.039, 0.055, 1.0],
129            surface: [0.067, 0.071, 0.090, 1.0],
130            surface_elevated: [0.094, 0.102, 0.129, 1.0],
131            surface_interactive: [0.133, 0.145, 0.188, 1.0],
132            surface_active: [0.164, 0.177, 0.220, 1.0],
133
134            // Lines
135            separator: [0.149, 0.165, 0.220, 1.0],
136            border: [0.180, 0.196, 0.255, 1.0],
137            border_strong: [0.260, 0.280, 0.345, 1.0],
138
139            // Text
140            text: [0.886, 0.910, 0.941, 1.0],
141            text_dim: [0.612, 0.647, 0.706, 1.0],
142            text_muted: [0.420, 0.455, 0.518, 1.0],
143            text_disabled: [0.300, 0.325, 0.380, 1.0],
144            text_inverse: [0.055, 0.063, 0.086, 1.0],
145
146            // Brand / accents
147            primary: [0.227, 0.529, 0.941, 1.0],
148            primary_dim: [0.098, 0.255, 0.510, 1.0],
149            accent_a: [0.486, 0.361, 0.871, 1.0],
150            accent_b: [0.392, 0.741, 0.918, 1.0],
151            accent_c: [0.953, 0.788, 0.353, 1.0],
152
153            // Status
154            success: [0.227, 0.722, 0.478, 1.0],
155            warning: [0.941, 0.627, 0.227, 1.0],
156            error: [0.941, 0.353, 0.227, 1.0],
157
158            // Axes
159            axis_x: [0.890, 0.310, 0.250, 1.0],
160            axis_y: [0.450, 0.820, 0.380, 1.0],
161            axis_z: [0.310, 0.560, 0.890, 1.0],
162
163            // Radii
164            radius_sm: 4.0,
165            radius_md: 6.0,
166            radius_lg: 10.0,
167            radius_xl: 14.0,
168
169            // Type
170            font_size_caption: 10.5,
171            font_size_body: 12.0,
172            font_size_title: 14.0,
173            font_size_display: 18.0,
174
175            // Spacing
176            pad_row: 8.0,
177            pad_card: 14.0,
178        }
179    }
180}