Skip to main content

dear_imgui_rs/style/
theme.rs

1use super::{Style, StyleColor};
2use crate::Context;
3use crate::internal::RawWrapper;
4use crate::sys;
5use crate::widget::{TableFlags, TableRowFlags};
6use crate::window::WindowFlags;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10/// Which base preset to start from when applying a [`Theme`].
11///
12/// This controls which built-in Dear ImGui color set is used as a starting
13/// point before applying any overrides.
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub enum ThemePreset {
17    /// Do not touch existing style colors; only apply explicit overrides.
18    None,
19    /// Use Dear ImGui's built-in dark preset.
20    Dark,
21    /// Use Dear ImGui's built-in light preset.
22    Light,
23    /// Use Dear ImGui's classic preset.
24    Classic,
25}
26
27impl Default for ThemePreset {
28    fn default() -> Self {
29        ThemePreset::None
30    }
31}
32
33/// A single color override for a given [`StyleColor`] entry.
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[derive(Clone, Debug, PartialEq)]
36pub struct ColorOverride {
37    /// Target style color to override.
38    pub id: StyleColor,
39    /// New RGBA color (0.0-1.0 range) to apply.
40    pub rgba: [f32; 4],
41}
42
43/// High-level style tweaks that can be applied on top of a preset.
44///
45/// This does not expose the full `ImGuiStyle` surface, only the most commonly
46/// themed fields. All fields are optional; `None` means "leave unchanged".
47#[derive(Clone, Debug)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49#[cfg_attr(feature = "serde", serde(default))]
50pub struct StyleTweaks {
51    pub window_rounding: Option<f32>,
52    pub frame_rounding: Option<f32>,
53    pub tab_rounding: Option<f32>,
54
55    pub window_padding: Option<[f32; 2]>,
56    pub frame_padding: Option<[f32; 2]>,
57    pub cell_padding: Option<[f32; 2]>,
58    pub item_spacing: Option<[f32; 2]>,
59    pub item_inner_spacing: Option<[f32; 2]>,
60
61    pub scrollbar_size: Option<f32>,
62    pub grab_min_size: Option<f32>,
63
64    pub indent_spacing: Option<f32>,
65    pub separator_size: Option<f32>,
66    pub scrollbar_rounding: Option<f32>,
67    pub grab_rounding: Option<f32>,
68    pub window_border_size: Option<f32>,
69    pub child_border_size: Option<f32>,
70    pub popup_border_size: Option<f32>,
71    pub frame_border_size: Option<f32>,
72    pub tab_border_size: Option<f32>,
73    pub child_rounding: Option<f32>,
74    pub popup_rounding: Option<f32>,
75
76    pub anti_aliased_lines: Option<bool>,
77    pub anti_aliased_fill: Option<bool>,
78}
79
80impl Default for StyleTweaks {
81    fn default() -> Self {
82        Self {
83            window_rounding: None,
84            frame_rounding: None,
85            tab_rounding: None,
86            window_padding: None,
87            frame_padding: None,
88            cell_padding: None,
89            item_spacing: None,
90            item_inner_spacing: None,
91            scrollbar_size: None,
92            grab_min_size: None,
93            indent_spacing: None,
94            separator_size: None,
95            scrollbar_rounding: None,
96            grab_rounding: None,
97            window_border_size: None,
98            child_border_size: None,
99            popup_border_size: None,
100            frame_border_size: None,
101            tab_border_size: None,
102            child_rounding: None,
103            popup_rounding: None,
104            anti_aliased_lines: None,
105            anti_aliased_fill: None,
106        }
107    }
108}
109
110impl StyleTweaks {
111    /// Apply these tweaks to the given style.
112    pub fn apply(&self, style: &mut Style) {
113        if let Some(v) = self.window_rounding {
114            style.set_window_rounding(v);
115        }
116        if let Some(v) = self.frame_rounding {
117            style.set_frame_rounding(v);
118        }
119        if let Some(v) = self.tab_rounding {
120            style.set_tab_rounding(v);
121        }
122
123        if let Some(v) = self.window_padding {
124            style.set_window_padding(v);
125        }
126        if let Some(v) = self.frame_padding {
127            style.set_frame_padding(v);
128        }
129        if let Some(v) = self.cell_padding {
130            style.set_cell_padding(v);
131        }
132        if let Some(v) = self.item_spacing {
133            style.set_item_spacing(v);
134        }
135        if let Some(v) = self.item_inner_spacing {
136            style.set_item_inner_spacing(v);
137        }
138
139        if let Some(v) = self.scrollbar_size {
140            style.set_scrollbar_size(v);
141        }
142        if let Some(v) = self.grab_min_size {
143            style.set_grab_min_size(v);
144        }
145
146        if let Some(v) = self.indent_spacing {
147            style.set_indent_spacing(v);
148        }
149        if let Some(v) = self.separator_size {
150            style.set_separator_size(v);
151        }
152        if let Some(v) = self.scrollbar_rounding {
153            style.set_scrollbar_rounding(v);
154        }
155        if let Some(v) = self.grab_rounding {
156            style.set_grab_rounding(v);
157        }
158        if let Some(v) = self.window_border_size {
159            style.set_window_border_size(v);
160        }
161        if let Some(v) = self.child_border_size {
162            style.set_child_border_size(v);
163        }
164        if let Some(v) = self.popup_border_size {
165            style.set_popup_border_size(v);
166        }
167        if let Some(v) = self.frame_border_size {
168            style.set_frame_border_size(v);
169        }
170        if let Some(v) = self.tab_border_size {
171            style.set_tab_border_size(v);
172        }
173        if let Some(v) = self.child_rounding {
174            style.set_child_rounding(v);
175        }
176        if let Some(v) = self.popup_rounding {
177            style.set_popup_rounding(v);
178        }
179
180        if let Some(v) = self.anti_aliased_lines {
181            style.set_anti_aliased_lines(v);
182        }
183        if let Some(v) = self.anti_aliased_fill {
184            style.set_anti_aliased_fill(v);
185        }
186    }
187}
188
189/// Window-related theme defaults (flags/behavior).
190#[derive(Clone, Debug, Default)]
191#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
192pub struct WindowTheme {
193    /// Default flags for top-level windows.
194    pub default_window_flags: Option<WindowFlags>,
195    /// Default flags for popups/modals.
196    pub popup_window_flags: Option<WindowFlags>,
197}
198
199/// Table-related theme defaults (flags/behavior).
200#[derive(Clone, Debug, Default)]
201#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
202pub struct TableTheme {
203    /// Default flags for tables created via `Ui::table` / `Ui::begin_table`.
204    pub default_table_flags: Option<TableFlags>,
205    /// Default row flags for data tables.
206    pub default_row_flags: Option<TableRowFlags>,
207}
208
209/// High-level theme configuration for Dear ImGui.
210///
211/// A theme is applied in three stages:
212/// 1) Choose a base preset (`Dark`/`Light`/`Classic` or `None`).
213/// 2) Apply any explicit color overrides.
214/// 3) Apply a small set of style tweaks.
215///
216/// Window/table defaults are provided as data and can be used by higher-level
217/// helpers when building windows and tables.
218#[derive(Clone, Debug, Default)]
219#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
220pub struct Theme {
221    /// Base preset to start from, before applying overrides.
222    #[cfg_attr(feature = "serde", serde(default))]
223    pub preset: ThemePreset,
224
225    /// Color overrides on top of the preset.
226    #[cfg_attr(feature = "serde", serde(default))]
227    pub colors: Vec<ColorOverride>,
228
229    /// Optional style tweaks on top of the preset.
230    #[cfg_attr(feature = "serde", serde(default))]
231    pub style: StyleTweaks,
232
233    /// Window-related defaults (flags/behavior).
234    #[cfg_attr(feature = "serde", serde(default))]
235    pub windows: WindowTheme,
236
237    /// Table-related defaults (flags/behavior).
238    #[cfg_attr(feature = "serde", serde(default))]
239    pub tables: TableTheme,
240}
241
242impl Theme {
243    /// Apply this theme to a given style.
244    ///
245    /// This does not touch fonts or IO; it only updates `ImGuiStyle`.
246    pub fn apply_to_style(&self, style: &mut Style) {
247        // 1) Base preset
248        match self.preset {
249            ThemePreset::None => {}
250            ThemePreset::Dark => unsafe {
251                sys::igStyleColorsDark(style.raw_mut());
252            },
253            ThemePreset::Light => unsafe {
254                sys::igStyleColorsLight(style.raw_mut());
255            },
256            ThemePreset::Classic => unsafe {
257                sys::igStyleColorsClassic(style.raw_mut());
258            },
259        }
260
261        // 2) Color overrides
262        for c in &self.colors {
263            style.set_color(c.id, c.rgba);
264        }
265
266        // 3) Common style tweaks
267        self.style.apply(style);
268    }
269
270    /// Apply this theme to the given context (current style).
271    pub fn apply_to_context(&self, ctx: &mut Context) {
272        let style = ctx.style_mut();
273        self.apply_to_style(style);
274    }
275}