Skip to main content

egui_charts/ui_kit/ext/
response_ext.rs

1//! ResponseExt - Extension trait for egui::Response
2//!
3//! Provides convenience methods for handling interaction responses.
4//!
5//! # Categories
6//!
7//! - **Click handlers**: on_click, on_double_click, on_triple_click, on_secondary_click, on_middle_click
8//! - **Hover handlers**: on_hover
9//! - **State queries**: is_double_clicked, is_middle_clicked
10//! - **UI helpers**: highlight, show_tooltip_on_hover
11//! - **Cursor**: set_cursor_icon
12
13use crate::localization as l10n;
14use crate::theme::ThemeExt;
15use crate::tokens::DESIGN_TOKENS;
16use egui::{CursorIcon, Response, Ui};
17
18/// Extension trait for `egui::Response` providing convenience methods
19///
20/// Note: Some egui Response methods consume self. For those methods,
21/// use the egui Response methods directly rather than these extensions.
22pub trait ResponseExt {
23    // ==========================================================================
24    // Click Handlers
25    // ==========================================================================
26
27    /// Handle single-click events (convenience method)
28    fn on_click(&self, action: impl FnOnce()) -> &Self;
29
30    /// Handle double-click events
31    fn on_double_click(&self, action: impl FnOnce()) -> &Self;
32
33    /// Handle triple-click events
34    fn on_triple_click(&self, action: impl FnOnce()) -> &Self;
35
36    /// Handle secondary click (right-click) events
37    fn on_secondary_click(&self, action: impl FnOnce()) -> &Self;
38
39    /// Handle middle click events
40    fn on_middle_click(&self, action: impl FnOnce()) -> &Self;
41
42    // ==========================================================================
43    // Hover Handlers
44    // ==========================================================================
45
46    /// Handle hover events
47    fn on_hover(&self, action: impl FnOnce()) -> &Self;
48
49    /// Handle hover start (entering hover state)
50    fn on_hover_start(&self, action: impl FnOnce()) -> &Self;
51
52    // ==========================================================================
53    // State Queries
54    // ==========================================================================
55
56    /// Check if the response was double-clicked (alias for double_clicked)
57    fn is_double_clicked(&self) -> bool;
58
59    /// Check if the response was middle-clicked (alias for middle_clicked)
60    fn is_middle_clicked(&self) -> bool;
61
62    /// Check if the response was secondary-clicked (alias for secondary_clicked)
63    fn is_secondary_clicked(&self) -> bool;
64
65    /// Check if drag started this frame
66    fn drag_started(&self) -> bool;
67
68    /// Check if drag ended this frame
69    fn drag_stopped(&self) -> bool;
70
71    // ==========================================================================
72    // UI Helpers
73    // ==========================================================================
74
75    /// Highlight the response rect (for debugging or emphasis)
76    fn highlight(&self, ui: &mut Ui) -> &Self;
77
78    /// Show a tooltip on hover with the given text
79    fn show_tooltip_on_hover(&self, text: impl Into<String>) -> &Self;
80
81    // ==========================================================================
82    // Cursor
83    // ==========================================================================
84
85    /// Set cursor icon when hovering over this response
86    fn set_cursor_icon(&self, icon: CursorIcon) -> &Self;
87}
88
89impl ResponseExt for Response {
90    // ==========================================================================
91    // Click Handlers
92    // ==========================================================================
93
94    fn on_click(&self, action: impl FnOnce()) -> &Self {
95        if self.clicked() {
96            action();
97        }
98        self
99    }
100
101    fn on_double_click(&self, action: impl FnOnce()) -> &Self {
102        if self.double_clicked() {
103            action();
104        }
105        self
106    }
107
108    fn on_triple_click(&self, action: impl FnOnce()) -> &Self {
109        if self.triple_clicked() {
110            action();
111        }
112        self
113    }
114
115    fn on_secondary_click(&self, action: impl FnOnce()) -> &Self {
116        if self.secondary_clicked() {
117            action();
118        }
119        self
120    }
121
122    fn on_middle_click(&self, action: impl FnOnce()) -> &Self {
123        if self.middle_clicked() {
124            action();
125        }
126        self
127    }
128
129    // ==========================================================================
130    // Hover Handlers
131    // ==========================================================================
132
133    fn on_hover(&self, action: impl FnOnce()) -> &Self {
134        if self.hovered() {
135            action();
136        }
137        self
138    }
139
140    fn on_hover_start(&self, action: impl FnOnce()) -> &Self {
141        // Hovered this frame but not last frame
142        if self.hovered() && !self.ctx.input(|i| i.pointer.any_pressed()) {
143            // This is a simple approximation - egui doesn't have a built-in "just started hovering"
144            // but we can use the response's "sense" timing for this
145            action();
146        }
147        self
148    }
149
150    // ==========================================================================
151    // State Queries
152    // ==========================================================================
153
154    fn is_double_clicked(&self) -> bool {
155        self.double_clicked()
156    }
157
158    fn is_middle_clicked(&self) -> bool {
159        self.middle_clicked()
160    }
161
162    fn is_secondary_clicked(&self) -> bool {
163        self.secondary_clicked()
164    }
165
166    fn drag_started(&self) -> bool {
167        self.drag_started_by(egui::PointerButton::Primary)
168    }
169
170    fn drag_stopped(&self) -> bool {
171        self.drag_stopped_by(egui::PointerButton::Primary)
172    }
173
174    // ==========================================================================
175    // UI Helpers
176    // ==========================================================================
177
178    fn highlight(&self, ui: &mut Ui) -> &Self {
179        let rect = self.rect;
180        let stroke = egui::Stroke::new(DESIGN_TOKENS.stroke.thick, ui.active_theme().ui.warning);
181        ui.painter()
182            .rect_stroke(rect, 0.0, stroke, egui::StrokeKind::Outside);
183        self
184    }
185
186    fn show_tooltip_on_hover(&self, text: impl Into<String>) -> &Self {
187        let _ = self
188            .clone()
189            .on_hover_text(l10n::text(&self.ctx, Into::<String>::into(text)));
190        self
191    }
192
193    // ==========================================================================
194    // Cursor
195    // ==========================================================================
196
197    fn set_cursor_icon(&self, icon: CursorIcon) -> &Self {
198        if self.hovered() {
199            self.ctx.set_cursor_icon(icon);
200        }
201        self
202    }
203}