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}