fltk_egui/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(clippy::all)]
3
4use std::{sync::Arc, time::Instant};
5
6use egui::{pos2, vec2, CursorIcon, Event, Key, Modifiers, MouseWheelUnit, Pos2, RawInput, Rect};
7use egui_glow::{glow, Painter};
8use egui_image::RetainedEguiImage;
9use fltk::{
10    app, enums,
11    prelude::{FltkError, ImageExt, WidgetExt, WindowExt},
12    window::GlWindow,
13};
14
15mod clipboard;
16mod egui_image;
17use clipboard::Clipboard;
18
19/// Construct the backend.
20pub fn init(win: &mut GlWindow) -> (Painter, EguiState) {
21    app::set_screen_scale(win.screen_num(), 1.);
22    app::keyboard_screen_scaling(false);
23    let gl = unsafe { glow::Context::from_loader_function(|s| win.get_proc_address(s) as _) };
24    let painter = Painter::new(Arc::from(gl), "", None, false)
25        .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
26    let max_texture_side = painter.max_texture_side();
27    (painter, EguiState::new(&win, max_texture_side))
28}
29
30/// Frame time for FPS.
31pub fn get_frame_time(start_time: Instant) -> f32 {
32    (Instant::now() - start_time).as_secs_f64() as f32
33}
34
35/// Casting slice to another type of slice
36pub fn cast_slice<T, D>(s: &[T]) -> &[D] {
37    unsafe {
38        std::slice::from_raw_parts(s.as_ptr() as *const D, s.len() * std::mem::size_of::<T>())
39    }
40}
41
42/// The default cursor
43pub struct FusedCursor {
44    pub cursor_icon: fltk::enums::Cursor,
45}
46
47const ARROW: enums::Cursor = enums::Cursor::Arrow;
48
49impl FusedCursor {
50    /// Construct a new cursor
51    pub fn new() -> Self {
52        Self { cursor_icon: ARROW }
53    }
54}
55
56impl Default for FusedCursor {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62/// Shuttles FLTK's input and events to Egui
63pub struct EguiState {
64    pub canvas_size: [u32; 2],
65    pub clipboard: Clipboard,
66    pub fuse_cursor: FusedCursor,
67    /// Use state.input.take() use this fn instead (to avoid pixels per point miscalculation).
68    pub input: RawInput,
69    _pixels_per_point: f32,
70    pub pointer_pos: Pos2,
71    /// default value is 12.0
72    pub scroll_factor: f32,
73    /// default value is 8.0
74    pub zoom_factor: f32,
75    /// Internal use case for fn window_resized()
76    _window_resized: bool,
77    pub max_texture_side: usize,
78}
79
80impl EguiState {
81    /// Construct a new state
82    pub fn new(win: &GlWindow, max_texture_side: usize) -> EguiState {
83        let ppu = win.pixels_per_unit();
84        let (width, height) = (win.width(), win.height());
85        let rect = vec2(width as f32, height as f32) / ppu;
86        let screen_rect = Rect::from_min_size(Pos2::new(0f32, 0f32), rect);
87        EguiState {
88            canvas_size: [width as u32, height as u32],
89            clipboard: Clipboard::default(),
90            fuse_cursor: FusedCursor::new(),
91            input: egui::RawInput {
92                screen_rect: Some(screen_rect),
93                // pixels_per_point: Some(ppu),
94                max_texture_side: Some(max_texture_side),
95                ..Default::default()
96            },
97            max_texture_side,
98            _pixels_per_point: ppu,
99            pointer_pos: Pos2::new(0f32, 0f32),
100            scroll_factor: 12.0,
101            zoom_factor: 8.0,
102            _window_resized: false,
103        }
104    }
105
106    pub fn take_input(&mut self) -> egui::RawInput {
107        self.input.max_texture_side = Some(self.max_texture_side);
108        // let pixels_per_point = self.input.pixels_per_point;
109        let take = self.input.take();
110        // self.input.pixels_per_point = pixels_per_point;
111        // if let Some(ppp) = pixels_per_point {
112        //     self._pixels_per_point = ppp;
113        // }
114        take
115    }
116
117    pub fn pixels_per_point(&self) -> f32 {
118        self._pixels_per_point
119    }
120
121    /// Check if current window being resized.
122    pub fn window_resized(&mut self) -> bool {
123        let tmp = self._window_resized;
124        self._window_resized = false;
125        tmp
126    }
127
128    /// Conveniece method bundling the necessary components for input/event handling
129    pub fn fuse_input(&mut self, win: &mut GlWindow, event: enums::Event) {
130        input_to_egui(win, event, self);
131    }
132
133    /// Convenience method for outputting what egui emits each frame
134    pub fn fuse_output(&mut self, win: &mut GlWindow, egui_output: egui::PlatformOutput) {
135        if !egui_output.copied_text.is_empty() {
136            self.clipboard.set(egui_output.copied_text);
137        }
138        if win.damage() {
139            win.clear_damage();
140        }
141        translate_cursor(win, &mut self.fuse_cursor, egui_output.cursor_icon);
142    }
143
144    /// Convenience method for outputting what egui emits each frame (borrow PlatformOutput)
145    pub fn fuse_output_borrow(&mut self, win: &mut GlWindow, egui_output: &egui::PlatformOutput) {
146        if !egui_output.copied_text.is_empty() {
147            app::copy(&egui_output.copied_text);
148        }
149        if win.damage() {
150            win.clear_damage();
151        }
152        translate_cursor(win, &mut self.fuse_cursor, egui_output.cursor_icon);
153    }
154
155    /// Set visual scale, e.g: 0.8, 1.5, 2.0 .etc (default is 1.0)
156    pub fn set_visual_scale(&mut self, size: f32) {
157        // have to be setted the pixels_per_point of both the inner (input) and the state.
158        // self.input.pixels_per_point = Some(size);
159        self._pixels_per_point = size;
160
161        // resize rect with canvas.
162        let canvas_size = self.canvas_size;
163        let rect = vec2(canvas_size[0] as f32, canvas_size[1] as f32) / size;
164        self.input.screen_rect = Some(Rect::from_min_size(Default::default(), rect));
165    }
166}
167
168/// Handles input/events from FLTK
169pub fn input_to_egui(
170    win: &mut GlWindow,
171    event: enums::Event,
172    state: &mut EguiState,
173    // painter: &mut Painter,
174) {
175    match event {
176        enums::Event::Resize => {
177            state.canvas_size = [win.width() as u32, win.height() as u32];
178            state.set_visual_scale(state.pixels_per_point());
179            state._window_resized = true;
180        }
181
182        //MouseButonLeft pressed is the only one needed by egui
183        enums::Event::Push => {
184            let mouse_btn = match app::event_mouse_button() {
185                app::MouseButton::Left => Some(egui::PointerButton::Primary),
186                app::MouseButton::Middle => Some(egui::PointerButton::Middle),
187                app::MouseButton::Right => Some(egui::PointerButton::Secondary),
188                _ => None,
189            };
190            if let Some(pressed) = mouse_btn {
191                state.input.events.push(egui::Event::PointerButton {
192                    pos: state.pointer_pos,
193                    button: pressed,
194                    pressed: true,
195                    modifiers: state.input.modifiers,
196                })
197            }
198        }
199
200        //MouseButonLeft pressed is the only one needed by egui
201        enums::Event::Released => {
202            // fix unreachable, we can use Option.
203            let mouse_btn = match app::event_mouse_button() {
204                app::MouseButton::Left => Some(egui::PointerButton::Primary),
205                app::MouseButton::Middle => Some(egui::PointerButton::Middle),
206                app::MouseButton::Right => Some(egui::PointerButton::Secondary),
207                _ => None,
208            };
209            if let Some(released) = mouse_btn {
210                state.input.events.push(egui::Event::PointerButton {
211                    pos: state.pointer_pos,
212                    button: released,
213                    pressed: false,
214                    modifiers: state.input.modifiers,
215                })
216            }
217        }
218
219        enums::Event::Move | enums::Event::Drag => {
220            let ppp = state.pixels_per_point();
221            let (x, y) = app::event_coords();
222            state.pointer_pos = pos2(x as f32 / ppp, y as f32 / ppp);
223            state
224                .input
225                .events
226                .push(egui::Event::PointerMoved(state.pointer_pos))
227        }
228
229        enums::Event::KeyUp => {
230            if let Some(key) = translate_virtual_key_code(app::event_key()) {
231                let keymod = app::event_state();
232                state.input.modifiers = Modifiers {
233                    alt: (keymod & enums::EventState::Alt == enums::EventState::Alt),
234                    ctrl: (keymod & enums::EventState::Ctrl == enums::EventState::Ctrl),
235                    shift: (keymod & enums::EventState::Shift == enums::EventState::Shift),
236                    mac_cmd: keymod & enums::EventState::Meta == enums::EventState::Meta,
237
238                    //TOD: Test on both windows and mac
239                    command: (keymod & enums::EventState::Command == enums::EventState::Command),
240                };
241                if state.input.modifiers.command && key == Key::V {
242                    if let Some(value) = state.clipboard.get() {
243                        state.input.events.push(egui::Event::Text(value));
244                    }
245                }
246            }
247        }
248
249        enums::Event::KeyDown => {
250            if let Some(c) = app::event_text().chars().next() {
251                if let Some(del) = app::compose() {
252                    state.input.events.push(Event::Text(c.to_string()));
253                    if del != 0 {
254                        app::compose_reset();
255                    }
256                }
257            }
258            if let Some(key) = translate_virtual_key_code(app::event_key()) {
259                let keymod = app::event_state();
260                state.input.modifiers = Modifiers {
261                    alt: (keymod & enums::EventState::Alt == enums::EventState::Alt),
262                    ctrl: (keymod & enums::EventState::Ctrl == enums::EventState::Ctrl),
263                    shift: (keymod & enums::EventState::Shift == enums::EventState::Shift),
264                    mac_cmd: keymod & enums::EventState::Meta == enums::EventState::Meta,
265
266                    //TOD: Test on both windows and mac
267                    command: (keymod & enums::EventState::Command == enums::EventState::Command),
268                };
269
270                state.input.events.push(Event::Key {
271                    key,
272                    physical_key: None,
273                    pressed: true,
274                    modifiers: state.input.modifiers,
275                    repeat: false,
276                });
277
278                if state.input.modifiers.command && key == Key::C {
279                    // println!("copy event");
280                    state.input.events.push(Event::Copy)
281                } else if state.input.modifiers.command && key == Key::X {
282                    // println!("cut event");
283                    state.input.events.push(Event::Cut)
284                } else {
285                    state.input.events.push(Event::Key {
286                        key,
287                        physical_key: None,
288                        pressed: false,
289                        modifiers: state.input.modifiers,
290                        repeat: false,
291                    })
292                }
293            }
294        }
295
296        enums::Event::MouseWheel => {
297            let keymod = app::event_state();
298            state.input.modifiers = Modifiers {
299                alt: (keymod & enums::EventState::Alt == enums::EventState::Alt),
300                ctrl: (keymod & enums::EventState::Ctrl == enums::EventState::Ctrl),
301                shift: (keymod & enums::EventState::Shift == enums::EventState::Shift),
302                mac_cmd: keymod & enums::EventState::Meta == enums::EventState::Meta,
303
304                //TOD: Test on both windows and mac
305                command: (keymod & enums::EventState::Command == enums::EventState::Command),
306            };
307            let negx = match app::event_dx() {
308                app::MouseWheel::Right => 1.,
309                app::MouseWheel::Left => -1.,
310                _ => 0.,
311            };
312            let negy = match app::event_dy() {
313                app::MouseWheel::Up => -1.,
314                app::MouseWheel::Down => 1.,
315                _ => 0.,
316            };
317            state.input.events.push(Event::MouseWheel {
318                unit: MouseWheelUnit::Line,
319                delta: vec2(1. * negx, 1. * negy) ,
320                modifiers: state.input.modifiers,
321            });
322        }
323        _ => {
324            //dbg!(event);
325        }
326    }
327}
328
329/// Translates key codes
330pub fn translate_virtual_key_code(key: enums::Key) -> Option<egui::Key> {
331    match key {
332        enums::Key::Left => Some(egui::Key::ArrowLeft),
333        enums::Key::Up => Some(egui::Key::ArrowUp),
334        enums::Key::Right => Some(egui::Key::ArrowRight),
335        enums::Key::Down => Some(egui::Key::ArrowDown),
336        enums::Key::Escape => Some(egui::Key::Escape),
337        enums::Key::Tab => Some(egui::Key::Tab),
338        enums::Key::BackSpace => Some(egui::Key::Backspace),
339        enums::Key::Insert => Some(egui::Key::Insert),
340        enums::Key::Home => Some(egui::Key::Home),
341        enums::Key::Delete => Some(egui::Key::Delete),
342        enums::Key::End => Some(egui::Key::End),
343        enums::Key::PageDown => Some(egui::Key::PageDown),
344        enums::Key::PageUp => Some(egui::Key::PageUp),
345        enums::Key::Enter => Some(egui::Key::Enter),
346        _ => {
347            if let Some(k) = key.to_char() {
348                match k {
349                    ' ' => Some(egui::Key::Space),
350                    'a' => Some(egui::Key::A),
351                    'b' => Some(egui::Key::B),
352                    'c' => Some(egui::Key::C),
353                    'd' => Some(egui::Key::D),
354                    'e' => Some(egui::Key::E),
355                    'f' => Some(egui::Key::F),
356                    'g' => Some(egui::Key::G),
357                    'h' => Some(egui::Key::H),
358                    'i' => Some(egui::Key::I),
359                    'j' => Some(egui::Key::J),
360                    'k' => Some(egui::Key::K),
361                    'l' => Some(egui::Key::L),
362                    'm' => Some(egui::Key::M),
363                    'n' => Some(egui::Key::N),
364                    'o' => Some(egui::Key::O),
365                    'p' => Some(egui::Key::P),
366                    'q' => Some(egui::Key::Q),
367                    'r' => Some(egui::Key::R),
368                    's' => Some(egui::Key::S),
369                    't' => Some(egui::Key::T),
370                    'u' => Some(egui::Key::U),
371                    'v' => Some(egui::Key::V),
372                    'w' => Some(egui::Key::W),
373                    'x' => Some(egui::Key::X),
374                    'y' => Some(egui::Key::Y),
375                    'z' => Some(egui::Key::Z),
376                    '0' => Some(egui::Key::Num0),
377                    '1' => Some(egui::Key::Num1),
378                    '2' => Some(egui::Key::Num2),
379                    '3' => Some(egui::Key::Num3),
380                    '4' => Some(egui::Key::Num4),
381                    '5' => Some(egui::Key::Num5),
382                    '6' => Some(egui::Key::Num6),
383                    '7' => Some(egui::Key::Num7),
384                    '8' => Some(egui::Key::Num8),
385                    '9' => Some(egui::Key::Num9),
386                    _ => None,
387                }
388            } else {
389                None
390            }
391        }
392    }
393}
394
395/// Translates FLTK cursor to Egui cursors
396pub fn translate_cursor(
397    win: &mut GlWindow,
398    fused: &mut FusedCursor,
399    cursor_icon: egui::CursorIcon,
400) {
401    let tmp_icon = match cursor_icon {
402        CursorIcon::None => enums::Cursor::None,
403        CursorIcon::Default => enums::Cursor::Arrow,
404        CursorIcon::Help => enums::Cursor::Help,
405        CursorIcon::PointingHand => enums::Cursor::Hand,
406        CursorIcon::ResizeHorizontal => enums::Cursor::WE,
407        CursorIcon::ResizeNeSw => enums::Cursor::NESW,
408        CursorIcon::ResizeNwSe => enums::Cursor::NWSE,
409        CursorIcon::ResizeVertical => enums::Cursor::NS,
410        CursorIcon::Text => enums::Cursor::Insert,
411        CursorIcon::Crosshair => enums::Cursor::Cross,
412        CursorIcon::NotAllowed | CursorIcon::NoDrop => enums::Cursor::Wait,
413        CursorIcon::Wait => enums::Cursor::Wait,
414        CursorIcon::Progress => enums::Cursor::Wait,
415        CursorIcon::Grab => enums::Cursor::Hand,
416        CursorIcon::Grabbing => enums::Cursor::Move,
417        CursorIcon::Move => enums::Cursor::Move,
418
419        _ => enums::Cursor::Arrow,
420    };
421
422    if tmp_icon != fused.cursor_icon {
423        fused.cursor_icon = tmp_icon;
424        win.set_cursor(tmp_icon)
425    }
426}
427
428pub trait EguiImageConvertible<I>
429where
430    I: ImageExt,
431{
432    fn egui_image(
433        self,
434        debug_name: &str,
435        options: egui::TextureOptions,
436    ) -> Result<RetainedEguiImage, FltkError>;
437}
438
439impl<I> EguiImageConvertible<I> for I
440where
441    I: ImageExt,
442{
443    /// Return (egui_extras::RetainedImage)
444    fn egui_image(
445        self,
446        debug_name: &str,
447        options: egui::TextureOptions,
448    ) -> Result<RetainedEguiImage, FltkError> {
449        let size = [self.data_w() as usize, self.data_h() as usize];
450        let color_image = egui::ColorImage::from_rgba_unmultiplied(
451            size,
452            &self
453                .to_rgb()?
454                .convert(enums::ColorDepth::Rgba8)?
455                .to_rgb_data(),
456        );
457
458        Ok(RetainedEguiImage::from_color_image(
459            debug_name,
460            color_image,
461            options,
462        ))
463    }
464}
465
466pub trait EguiSvgConvertible {
467    fn egui_svg_image(
468        self,
469        debug_name: &str,
470        options: egui::TextureOptions,
471    ) -> Result<RetainedEguiImage, FltkError>;
472}
473
474impl EguiSvgConvertible for fltk::image::SvgImage {
475    /// Return (egui_extras::RetainedEguiImage)
476    fn egui_svg_image(
477        mut self,
478        debug_name: &str,
479        options: egui::TextureOptions,
480    ) -> Result<RetainedEguiImage, FltkError> {
481        self.normalize();
482        let size = [self.data_w() as usize, self.data_h() as usize];
483        let color_image = egui::ColorImage::from_rgba_unmultiplied(
484            size,
485            &self
486                .to_rgb()?
487                .convert(enums::ColorDepth::Rgba8)?
488                .to_rgb_data(),
489        );
490
491        Ok(RetainedEguiImage::from_color_image(
492            debug_name,
493            color_image,
494            options,
495        ))
496    }
497}
498
499/// egui::ColorImage Extender.
500pub trait ColorImageExt {
501    fn from_vec_color32(size: [usize; 2], vec: Vec<egui::Color32>) -> Self;
502
503    fn from_color32_slice(size: [usize; 2], slice: &[egui::Color32]) -> Self;
504}
505
506impl ColorImageExt for egui::ColorImage {
507    fn from_vec_color32(size: [usize; 2], vec: Vec<egui::Color32>) -> Self {
508        let mut pixels: Vec<u8> = Vec::with_capacity(vec.len() * 4);
509        vec.into_iter().for_each(|x| {
510            pixels.push(x[0]);
511            pixels.push(x[1]);
512            pixels.push(x[2]);
513            pixels.push(x[3]);
514        });
515        egui::ColorImage::from_rgba_unmultiplied(size, &pixels)
516    }
517
518    fn from_color32_slice(size: [usize; 2], slice: &[egui::Color32]) -> Self {
519        let mut pixels: Vec<u8> = Vec::with_capacity(slice.len() * 4);
520        slice.into_iter().for_each(|x| {
521            pixels.push(x[0]);
522            pixels.push(x[1]);
523            pixels.push(x[2]);
524            pixels.push(x[3]);
525        });
526        egui::ColorImage::from_rgba_unmultiplied(size, &pixels)
527    }
528}
529
530/// egui::TextureHandle Extender.
531pub trait TextureHandleExt {
532    /// egui::TextureHandle from Vec u8
533    fn from_vec_u8(
534        ctx: &egui::Context,
535        debug_name: &str,
536        size: [usize; 2],
537        vec: Vec<u8>,
538        options: egui::TextureOptions,
539    ) -> egui::TextureHandle;
540
541    fn from_u8_slice(
542        ctx: &egui::Context,
543        debug_name: &str,
544        size: [usize; 2],
545        slice: &[u8],
546        options: egui::TextureOptions,
547    ) -> egui::TextureHandle;
548
549    fn from_vec_color32(
550        ctx: &egui::Context,
551        debug_name: &str,
552        size: [usize; 2],
553        vec: Vec<egui::Color32>,
554        options: egui::TextureOptions,
555    ) -> egui::TextureHandle;
556
557    fn from_color32_slice(
558        ctx: &egui::Context,
559        debug_name: &str,
560        size: [usize; 2],
561        slice: &[egui::Color32],
562        options: egui::TextureOptions,
563    ) -> egui::TextureHandle;
564}
565
566impl TextureHandleExt for egui::TextureHandle {
567    fn from_vec_u8(
568        ctx: &egui::Context,
569        debug_name: &str,
570        size: [usize; 2],
571        vec: Vec<u8>,
572        options: egui::TextureOptions,
573    ) -> Self {
574        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, &vec);
575        drop(vec);
576        ctx.load_texture(debug_name, color_image, options)
577    }
578
579    fn from_u8_slice(
580        ctx: &egui::Context,
581        debug_name: &str,
582        size: [usize; 2],
583        slice: &[u8],
584        options: egui::TextureOptions,
585    ) -> Self {
586        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, slice);
587        ctx.load_texture(debug_name, color_image, options)
588    }
589
590    fn from_vec_color32(
591        ctx: &egui::Context,
592        debug_name: &str,
593        size: [usize; 2],
594        vec: Vec<egui::Color32>,
595        options: egui::TextureOptions,
596    ) -> Self {
597        let mut pixels: Vec<u8> = Vec::with_capacity(vec.len() * 4);
598        vec.into_iter().for_each(|x| {
599            pixels.push(x[0]);
600            pixels.push(x[1]);
601            pixels.push(x[2]);
602            pixels.push(x[3]);
603        });
604        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
605        ctx.load_texture(debug_name, color_image, options)
606    }
607
608    fn from_color32_slice(
609        ctx: &egui::Context,
610        debug_name: &str,
611        size: [usize; 2],
612        slice: &[egui::Color32],
613        options: egui::TextureOptions,
614    ) -> Self {
615        let mut pixels: Vec<u8> = Vec::with_capacity(slice.len() * 4);
616        slice.into_iter().for_each(|x| {
617            pixels.push(x[0]);
618            pixels.push(x[1]);
619            pixels.push(x[2]);
620            pixels.push(x[3]);
621        });
622        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
623        ctx.load_texture(debug_name, color_image, options)
624    }
625}