Skip to main content

blockworx_egui/
convert.rs

1//! The toolkit boundary's dictionary: geom and paint ↔ egui, one conversion per
2//! type.
3//!
4//! Two extension traits rather than `From` impls, because neither
5//! `blockworx-geom` nor `blockworx-paint` names an egui-family crate — normal,
6//! build or dev — so neither side of a pair can own the impl. A call site says
7//! `.egui()` when it is about to hand a value to an egui API and `.geom()` when
8//! it has just taken one back, which reads as the crossing it is.
9
10use blockworx_geom::{Align, Align2, Pos2, Rangef, Rect, Vec2};
11use blockworx_paint::{
12    AnimKey, Base, Chord, Color, Cursor, EditId, Font, FontFamily, Key, Modifiers, Palette,
13};
14
15/// Geom → egui, at the point an egui API is called.
16pub trait IntoEgui {
17    type Out;
18
19    fn egui(self) -> Self::Out;
20}
21
22/// egui → geom, at the point a value comes back across the boundary.
23pub trait IntoGeom {
24    type Out;
25
26    fn geom(self) -> Self::Out;
27}
28
29/// One `x`/`y` pair to another.
30macro_rules! xy {
31    ($ours:ty, $theirs:ty) => {
32        impl IntoEgui for $ours {
33            type Out = $theirs;
34
35            #[inline]
36            fn egui(self) -> Self::Out {
37                <$theirs>::new(self.x, self.y)
38            }
39        }
40
41        impl IntoGeom for $theirs {
42            type Out = $ours;
43
44            #[inline]
45            fn geom(self) -> Self::Out {
46                <$ours>::new(self.x, self.y)
47            }
48        }
49    };
50}
51
52xy!(Pos2, egui::Pos2);
53xy!(Vec2, egui::Vec2);
54
55impl IntoEgui for Rect {
56    type Out = egui::Rect;
57
58    #[inline]
59    fn egui(self) -> Self::Out {
60        egui::Rect::from_min_max(self.min.egui(), self.max.egui())
61    }
62}
63
64impl IntoGeom for egui::Rect {
65    type Out = Rect;
66
67    #[inline]
68    fn geom(self) -> Self::Out {
69        Rect::from_min_max(self.min.geom(), self.max.geom())
70    }
71}
72
73impl IntoEgui for Rangef {
74    type Out = egui::Rangef;
75
76    #[inline]
77    fn egui(self) -> Self::Out {
78        egui::Rangef::new(self.min, self.max)
79    }
80}
81
82impl IntoGeom for egui::Rangef {
83    type Out = Rangef;
84
85    #[inline]
86    fn geom(self) -> Self::Out {
87        Rangef::new(self.min, self.max)
88    }
89}
90
91impl IntoEgui for Align {
92    type Out = egui::Align;
93
94    #[inline]
95    fn egui(self) -> Self::Out {
96        match self {
97            Align::Min => egui::Align::Min,
98            Align::Center => egui::Align::Center,
99            Align::Max => egui::Align::Max,
100        }
101    }
102}
103
104impl IntoGeom for egui::Align {
105    type Out = Align;
106
107    #[inline]
108    fn geom(self) -> Self::Out {
109        match self {
110            egui::Align::Min => Align::Min,
111            egui::Align::Center => Align::Center,
112            egui::Align::Max => Align::Max,
113        }
114    }
115}
116
117impl IntoEgui for Align2 {
118    type Out = egui::Align2;
119
120    #[inline]
121    fn egui(self) -> Self::Out {
122        egui::Align2([self.x().egui(), self.y().egui()])
123    }
124}
125
126impl IntoGeom for egui::Align2 {
127    type Out = Align2;
128
129    #[inline]
130    fn geom(self) -> Self::Out {
131        Align2([self.x().geom(), self.y().geom()])
132    }
133}
134
135impl IntoEgui for Color {
136    type Out = egui::Color32;
137
138    #[inline]
139    fn egui(self) -> Self::Out {
140        let [r, g, b, a] = self.to_array();
141        egui::Color32::from_rgba_premultiplied(r, g, b, a)
142    }
143}
144
145impl IntoGeom for egui::Color32 {
146    type Out = Color;
147
148    #[inline]
149    fn geom(self) -> Self::Out {
150        let [r, g, b, a] = self.to_array();
151        Color::from_rgba_premultiplied(r, g, b, a)
152    }
153}
154
155impl IntoEgui for FontFamily {
156    type Out = egui::FontFamily;
157
158    #[inline]
159    fn egui(self) -> Self::Out {
160        match self {
161            FontFamily::Proportional => egui::FontFamily::Proportional,
162            FontFamily::Monospace => egui::FontFamily::Monospace,
163            FontFamily::Named(name) => egui::FontFamily::Name(name),
164        }
165    }
166}
167
168impl IntoGeom for egui::FontFamily {
169    type Out = FontFamily;
170
171    #[inline]
172    fn geom(self) -> Self::Out {
173        match self {
174            egui::FontFamily::Proportional => FontFamily::Proportional,
175            egui::FontFamily::Monospace => FontFamily::Monospace,
176            egui::FontFamily::Name(name) => FontFamily::Named(name),
177        }
178    }
179}
180
181impl IntoEgui for Font {
182    type Out = egui::FontId;
183
184    #[inline]
185    fn egui(self) -> Self::Out {
186        egui::FontId::new(self.size, self.family.egui())
187    }
188}
189
190impl IntoEgui for &Font {
191    type Out = egui::FontId;
192
193    #[inline]
194    fn egui(self) -> Self::Out {
195        self.clone().egui()
196    }
197}
198
199impl IntoGeom for egui::FontId {
200    type Out = Font;
201
202    #[inline]
203    fn geom(self) -> Self::Out {
204        Font::new(self.size, self.family.geom())
205    }
206}
207
208impl IntoEgui for Cursor {
209    type Out = egui::CursorIcon;
210
211    #[inline]
212    fn egui(self) -> Self::Out {
213        match self {
214            Cursor::Default => egui::CursorIcon::Default,
215            Cursor::Crosshair => egui::CursorIcon::Crosshair,
216            Cursor::Text => egui::CursorIcon::Text,
217            Cursor::PointingHand => egui::CursorIcon::PointingHand,
218            Cursor::Move => egui::CursorIcon::Move,
219            Cursor::Grab => egui::CursorIcon::Grab,
220            Cursor::Grabbing => egui::CursorIcon::Grabbing,
221        }
222    }
223}
224
225impl IntoEgui for EditId {
226    type Out = egui::Id;
227
228    #[inline]
229    fn egui(self) -> Self::Out {
230        egui::Id::new(self.get())
231    }
232}
233
234impl IntoEgui for AnimKey {
235    type Out = egui::Id;
236
237    #[inline]
238    fn egui(self) -> Self::Out {
239        egui::Id::new(self.get())
240    }
241}
242
243impl IntoEgui for Key {
244    type Out = egui::Key;
245
246    #[inline]
247    fn egui(self) -> Self::Out {
248        match self {
249            Key::Num0 => egui::Key::Num0,
250            Key::Num1 => egui::Key::Num1,
251            Key::Num2 => egui::Key::Num2,
252            Key::Num3 => egui::Key::Num3,
253            Key::Num4 => egui::Key::Num4,
254            Key::Num5 => egui::Key::Num5,
255            Key::Num6 => egui::Key::Num6,
256            Key::Num7 => egui::Key::Num7,
257            Key::Num8 => egui::Key::Num8,
258            Key::B => egui::Key::B,
259            Key::E => egui::Key::E,
260            Key::I => egui::Key::I,
261            Key::K => egui::Key::K,
262            Key::M => egui::Key::M,
263            Key::N => egui::Key::N,
264            Key::P => egui::Key::P,
265            Key::R => egui::Key::R,
266            Key::T => egui::Key::T,
267            Key::Y => egui::Key::Y,
268            Key::Z => egui::Key::Z,
269            Key::Equals => egui::Key::Equals,
270            Key::Plus => egui::Key::Plus,
271            Key::Minus => egui::Key::Minus,
272            Key::ArrowLeft => egui::Key::ArrowLeft,
273            Key::ArrowRight => egui::Key::ArrowRight,
274            Key::ArrowUp => egui::Key::ArrowUp,
275            Key::ArrowDown => egui::Key::ArrowDown,
276        }
277    }
278}
279
280impl IntoEgui for Modifiers {
281    type Out = egui::Modifiers;
282
283    #[inline]
284    fn egui(self) -> Self::Out {
285        match self {
286            Modifiers::None => egui::Modifiers::NONE,
287            Modifiers::Command => egui::Modifiers::COMMAND,
288            Modifiers::CommandShift => egui::Modifiers::COMMAND | egui::Modifiers::SHIFT,
289        }
290    }
291}
292
293impl IntoEgui for Chord {
294    type Out = egui::KeyboardShortcut;
295
296    #[inline]
297    fn egui(self) -> Self::Out {
298        egui::KeyboardShortcut::new(self.modifiers.egui(), self.key.egui())
299    }
300}
301
302/// Build egui's [`Visuals`](egui::Visuals) from `palette` so the chrome egui
303/// draws itself — menus, buttons, checkboxes, separators and the in-place rename
304/// `TextEdit` — matches the canvas. Starts from the light or dark preset (per the
305/// scheme's own luminance) and overrides the colors that read against our
306/// background.
307pub fn visuals(palette: &Palette) -> egui::Visuals {
308    use egui::Stroke;
309    let c = |b| palette.get(b).egui();
310    let mut v = if palette.is_dark() {
311        egui::Visuals::dark()
312    } else {
313        egui::Visuals::light()
314    };
315
316    v.override_text_color = Some(c(Base::B05));
317    v.panel_fill = c(Base::B00);
318    v.window_fill = c(Base::B01);
319    v.window_stroke = Stroke::new(1.0, c(Base::B02));
320    v.extreme_bg_color = c(Base::B00); // text-edit background
321    v.faint_bg_color = c(Base::B01);
322    v.code_bg_color = c(Base::B01);
323    v.hyperlink_color = c(Base::B0D);
324    v.selection.bg_fill = c(Base::B0D).gamma_multiply(0.4);
325    v.selection.stroke = Stroke::new(1.0, c(Base::B05));
326
327    let w = &mut v.widgets;
328    w.noninteractive.bg_fill = c(Base::B01);
329    w.noninteractive.weak_bg_fill = c(Base::B01);
330    w.noninteractive.bg_stroke = Stroke::new(1.0, c(Base::B02));
331    w.noninteractive.fg_stroke = Stroke::new(1.0, c(Base::B05));
332
333    w.inactive.bg_fill = c(Base::B02);
334    w.inactive.weak_bg_fill = c(Base::B01);
335    w.inactive.bg_stroke = Stroke::new(1.0, c(Base::B02));
336    w.inactive.fg_stroke = Stroke::new(1.0, c(Base::B05));
337
338    w.hovered.bg_fill = c(Base::B03);
339    w.hovered.weak_bg_fill = c(Base::B02);
340    w.hovered.bg_stroke = Stroke::new(1.0, c(Base::B0D));
341    w.hovered.fg_stroke = Stroke::new(1.5, c(Base::B06));
342
343    w.active.bg_fill = c(Base::B0D);
344    w.active.weak_bg_fill = c(Base::B03);
345    w.active.bg_stroke = Stroke::new(1.0, c(Base::B0D));
346    w.active.fg_stroke = Stroke::new(2.0, c(Base::B00));
347
348    w.open.bg_fill = c(Base::B02);
349    w.open.weak_bg_fill = c(Base::B01);
350    w.open.bg_stroke = Stroke::new(1.0, c(Base::B02));
351    w.open.fg_stroke = Stroke::new(1.0, c(Base::B05));
352
353    v
354}
355
356#[cfg(test)]
357mod tests;