Skip to main content

jag_ui/widgets/
types.rs

1//! Shared widget types: colors, events, and cursor hints.
2
3use jag_draw::ColorLinPremul;
4
5use crate::ThemeMode;
6
7/// Cursor hint returned by widgets so the host can set the OS cursor.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum CursorHint {
10    Default,
11    ResizeHorizontal,
12    ResizeVertical,
13    Pointer,
14}
15
16/// Semantic events returned by widgets after processing input.
17#[derive(Debug, Clone, PartialEq)]
18pub enum WidgetEvent {
19    /// Input was not relevant to this widget.
20    Ignored,
21    /// Input was consumed but produced no semantic action.
22    Consumed,
23    /// Tab was selected by index.
24    TabSelected { index: usize },
25    /// Tab close requested by index.
26    TabClose { index: usize },
27    /// Popup menu item selected by index.
28    PopupItemSelected { index: usize },
29    /// Popup was dismissed (Escape or outside click).
30    PopupDismissed,
31    /// Widget requests a cursor change.
32    SetCursor(CursorHint),
33}
34
35/// Color palette for widget rendering, derived from [`ThemeMode`].
36#[derive(Debug, Clone, Copy)]
37pub struct WidgetColors {
38    /// Main background of panels / containers.
39    pub bg: ColorLinPremul,
40    /// Slightly lighter/darker surface (e.g. active tab, hovered row).
41    pub surface: ColorLinPremul,
42    /// Hover highlight color.
43    pub hover: ColorLinPremul,
44    /// Active/selected item background.
45    pub active: ColorLinPremul,
46    /// Accent underline / indicator color.
47    pub accent: ColorLinPremul,
48    /// Primary text color.
49    pub text: ColorLinPremul,
50    /// Muted / secondary text color.
51    pub text_muted: ColorLinPremul,
52    /// Border / separator line color.
53    pub border: ColorLinPremul,
54    /// Close / destructive icon color.
55    pub close_icon: ColorLinPremul,
56    /// Dirty-file indicator dot color.
57    pub dirty_dot: ColorLinPremul,
58    /// Shadow color for popups.
59    pub shadow: ColorLinPremul,
60}
61
62impl WidgetColors {
63    /// Derive widget colors from the given theme mode.
64    pub fn from_theme_mode(mode: ThemeMode) -> Self {
65        match mode {
66            ThemeMode::Dark => Self {
67                bg: ColorLinPremul::from_srgba_u8([22, 27, 47, 255]),
68                surface: ColorLinPremul::from_srgba_u8([30, 35, 55, 255]),
69                hover: ColorLinPremul::from_srgba_u8([40, 46, 70, 255]),
70                active: ColorLinPremul::from_srgba_u8([50, 56, 82, 255]),
71                accent: ColorLinPremul::from_srgba_u8([100, 140, 255, 255]),
72                text: ColorLinPremul::from_srgba_u8([220, 225, 240, 255]),
73                text_muted: ColorLinPremul::from_srgba_u8([140, 150, 170, 255]),
74                border: ColorLinPremul::from_srgba_u8([60, 65, 85, 255]),
75                close_icon: ColorLinPremul::from_srgba_u8([100, 110, 130, 255]),
76                dirty_dot: ColorLinPremul::from_srgba_u8([255, 200, 50, 255]),
77                shadow: ColorLinPremul::from_srgba_u8([0, 0, 0, 120]),
78            },
79            ThemeMode::Light => Self {
80                bg: ColorLinPremul::from_srgba_u8([248, 250, 252, 255]),
81                surface: ColorLinPremul::from_srgba_u8([241, 245, 249, 255]),
82                hover: ColorLinPremul::from_srgba_u8([226, 232, 240, 255]),
83                active: ColorLinPremul::from_srgba_u8([203, 213, 225, 255]),
84                accent: ColorLinPremul::from_srgba_u8([59, 130, 246, 255]),
85                text: ColorLinPremul::from_srgba_u8([15, 23, 42, 255]),
86                text_muted: ColorLinPremul::from_srgba_u8([100, 116, 139, 255]),
87                border: ColorLinPremul::from_srgba_u8([226, 232, 240, 255]),
88                close_icon: ColorLinPremul::from_srgba_u8([148, 163, 184, 255]),
89                dirty_dot: ColorLinPremul::from_srgba_u8([245, 158, 11, 255]),
90                shadow: ColorLinPremul::from_srgba_u8([0, 0, 0, 40]),
91            },
92        }
93    }
94}
95
96// ---------------------------------------------------------------------------
97// Tests
98// ---------------------------------------------------------------------------
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn widget_event_equality() {
106        assert_eq!(WidgetEvent::Ignored, WidgetEvent::Ignored);
107        assert_ne!(WidgetEvent::Ignored, WidgetEvent::Consumed);
108        assert_eq!(
109            WidgetEvent::TabSelected { index: 1 },
110            WidgetEvent::TabSelected { index: 1 }
111        );
112        assert_ne!(
113            WidgetEvent::TabSelected { index: 0 },
114            WidgetEvent::TabClose { index: 0 }
115        );
116    }
117
118    #[test]
119    fn widget_colors_dark_and_light_differ() {
120        let dark = WidgetColors::from_theme_mode(ThemeMode::Dark);
121        let light = WidgetColors::from_theme_mode(ThemeMode::Light);
122        // Background colors should differ between themes.
123        assert_ne!(dark.bg, light.bg);
124    }
125
126    #[test]
127    fn cursor_hint_variants() {
128        let hints = [
129            CursorHint::Default,
130            CursorHint::ResizeHorizontal,
131            CursorHint::ResizeVertical,
132            CursorHint::Pointer,
133        ];
134        for (i, a) in hints.iter().enumerate() {
135            for (j, b) in hints.iter().enumerate() {
136                if i != j {
137                    assert_ne!(a, b);
138                }
139            }
140        }
141    }
142}