egui_fltk_frontend/
lib.rs

1pub use egui;
2use egui::{pos2, vec2, CursorIcon, Event, Rect, Vec2};
3pub use egui_image::RetainedEguiImage;
4mod backend;
5pub use backend::{CallbackFn, RenderPass, ScreenDescriptor};
6pub use fltk;
7use fltk::{
8    app,
9    enums::{self, Cursor},
10    prelude::{FltkError, ImageExt, WindowExt},
11};
12pub use pollster;
13use std::time::Instant;
14pub use wgpu;
15mod clipboard;
16mod egui_image;
17use clipboard::Clipboard;
18
19/// Pixel per unit trait helper.
20pub trait PPU {
21    fn pixels_per_unit(&self) -> f32;
22}
23
24impl PPU for fltk::window::Window {
25    fn pixels_per_unit(&self) -> f32 {
26        self.pixels_per_unit()
27    }
28}
29
30#[cfg(feature = "enable-glwindow")]
31impl PPU for fltk::window::GlWindow {
32    fn pixels_per_unit(&self) -> f32 {
33        self.pixels_per_unit()
34    }
35}
36
37/// Construct the frontend.
38pub fn begin_with<'a, W>(
39    window: &mut W,
40    render_pass: RenderPass<'a>,
41    surface: wgpu::Surface,
42    surface_config: wgpu::SurfaceConfiguration,
43) -> (Painter<'a>, EguiState)
44where
45    W: WindowExt + PPU,
46{
47    app::set_screen_scale(window.screen_num(), 1.0);
48    app::keyboard_screen_scaling(false);
49    let ppu = window.pixels_per_unit();
50    let x = window.width();
51    let y = window.height();
52    let rect = egui::vec2(x as _, y as _) / ppu;
53    let screen_rect = egui::Rect::from_min_size(egui::Pos2::new(0f32, 0f32), rect);
54
55    let painter = Painter {
56        render_pass,
57        surface,
58        surface_config,
59        encoder: wgpu::CommandEncoderDescriptor {
60            label: Some("encoder"),
61        },
62    };
63
64    let state = EguiState {
65        _window_resized: false,
66        fuse_cursor: FusedCursor::new(),
67        pointer_pos: egui::Pos2::new(0.0, 0.0),
68        input: egui::RawInput {
69            screen_rect: Some(screen_rect),
70            pixels_per_point: Some(ppu),
71            ..Default::default()
72        },
73        clipboard: clipboard::Clipboard::default(),
74        _mouse_btn_pressed: false,
75        scroll_factor: 12.0,
76        zoom_factor: 8.0,
77        screen_descriptor: ScreenDescriptor {
78            size_in_pixels: [x as _, y as _],
79            pixels_per_point: ppu,
80        },
81    };
82    (painter, state)
83}
84
85pub struct Painter<'a> {
86    pub render_pass: RenderPass<'a>,
87    pub surface: wgpu::Surface,
88    pub surface_config: wgpu::SurfaceConfiguration,
89    encoder: wgpu::CommandEncoderDescriptor<'a>,
90}
91
92impl<'a> Painter<'a> {
93    /// Paint with egui renderpass
94    pub fn paint_with_rpass<'rpass>(
95        &'rpass mut self,
96        rpass: &mut wgpu::RenderPass<'rpass>,
97        device: &wgpu::Device,
98        queue: &wgpu::Queue,
99        screen_descriptor: &ScreenDescriptor,
100        clipped_primitive: Vec<egui::ClippedPrimitive>,
101        texture: egui::TexturesDelta,
102    ) {
103        if texture.free.len() > 0 {
104            texture.free.into_iter().for_each(|id| {
105                self.render_pass.free_texture(&id);
106            });
107        }
108
109        for (id, img_del) in texture.set {
110            self.render_pass.update_texture(device, queue, id, &img_del);
111        }
112
113        self.render_pass
114            .update_buffers(device, queue, &clipped_primitive, screen_descriptor);
115
116        self.render_pass
117            .execute_with_renderpass(rpass, clipped_primitive, screen_descriptor);
118    }
119
120    pub fn paint_jobs(
121        &mut self,
122        device: &wgpu::Device,
123        queue: &wgpu::Queue,
124        screen_descriptor: &ScreenDescriptor,
125        clipped_primitive: Vec<egui::ClippedPrimitive>,
126        texture: egui::TexturesDelta,
127    ) {
128        {
129            let size = screen_descriptor.size_in_pixels;
130            self.surface_config.width = size[0];
131            self.surface_config.height = size[1];
132            self.surface.configure(device, &self.surface_config);
133        }
134
135        // Record all render passes.
136        let output_frame = match self.surface.get_current_texture() {
137            Ok(frame) => {
138                let mut encoder = device.create_command_encoder(&self.encoder);
139
140                if texture.free.len() > 0 {
141                    texture.free.into_iter().for_each(|id| {
142                        self.render_pass.free_texture(&id);
143                    });
144                }
145
146                for (id, img_del) in texture.set {
147                    self.render_pass.update_texture(device, queue, id, &img_del);
148                }
149
150                self.render_pass.update_buffers(
151                    device,
152                    queue,
153                    &clipped_primitive,
154                    screen_descriptor,
155                );
156
157                self.render_pass.execute(
158                    &mut encoder,
159                    &frame.texture.create_view(&self.render_pass.tex_view_desc),
160                    clipped_primitive,
161                    screen_descriptor,
162                    Some(wgpu::Color::BLACK),
163                );
164
165                // Submit command buffer
166                let cm_buffer = encoder.finish();
167                queue.submit(Some(cm_buffer));
168                frame
169            }
170            Err(e) => return eprintln!("Dropped frame with error: {}", e),
171        };
172
173        // Draw finalize frame
174        output_frame.present();
175    }
176}
177
178/// Frame time for CPU usage.
179pub fn get_frame_time(start_time: Instant) -> f32 {
180    (Instant::now() - start_time).as_secs_f64() as _
181}
182
183/// The default cursor
184pub struct FusedCursor {
185    pub cursor_icon: Cursor,
186}
187
188const ARROW: enums::Cursor = enums::Cursor::Arrow;
189
190impl FusedCursor {
191    /// Construct a new cursor
192    pub fn new() -> Self {
193        Self { cursor_icon: ARROW }
194    }
195}
196
197impl Default for FusedCursor {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203/// Shuttles FLTK's input and events to Egui
204pub struct EguiState {
205    _window_resized: bool,
206    pub fuse_cursor: FusedCursor,
207    pub pointer_pos: egui::Pos2,
208    input: egui::RawInput,
209    pub clipboard: Clipboard,
210    /// default value is 12.0
211    pub scroll_factor: f32,
212    /// default value is 8.0
213    pub zoom_factor: f32,
214    _mouse_btn_pressed: bool,
215    pub screen_descriptor: ScreenDescriptor,
216}
217
218impl EguiState {
219    /// Conveniece method bundling the necessary components for input/event handling
220    pub fn fuse_input<W>(&mut self, win: &mut W, event: enums::Event)
221    where
222        W: WindowExt + PPU,
223    {
224        input_to_egui(win, event, self);
225    }
226
227    pub fn window_resized(&mut self) -> bool {
228        let tmp = self._window_resized;
229        self._window_resized = false;
230        tmp
231    }
232
233    pub fn mouse_btn_pressed(&self) -> bool {
234        self._mouse_btn_pressed
235    }
236
237    /// Convenience method for outputting what egui emits each frame
238    pub fn fuse_output<W>(&mut self, win: &mut W, egui_output: egui::PlatformOutput)
239    where
240        W: WindowExt,
241    {
242        if win.damage() {
243            win.clear_damage();
244        }
245
246        let copied_text = &egui_output.copied_text;
247        if !copied_text.is_empty() {
248            self.clipboard.set(copied_text.into());
249        }
250        translate_cursor(win, &mut self.fuse_cursor, egui_output.cursor_icon);
251    }
252
253    /// Set visual scale, e.g: 0.8, 1.5, 2.0 .etc (default is 1.0)
254    pub fn set_visual_scale(&mut self, size: f32) {
255        // have to be setted the pixels_per_point of both the inner (input) and the state.
256        self.input.pixels_per_point = Some(size);
257        self.screen_descriptor.pixels_per_point = size;
258
259        let size_in_pixels = self.screen_descriptor.size_in_pixels;
260
261        // resize rect with physical dimention size.
262        let rect = vec2(size_in_pixels[0] as _, size_in_pixels[1] as _) / size;
263        self.input.screen_rect = Some(Rect::from_min_size(Default::default(), rect));
264    }
265
266    pub fn pixels_per_point(&self) -> f32 {
267        self.screen_descriptor.pixels_per_point
268    }
269
270    pub fn take_input(&mut self) -> egui::RawInput {
271        let pixels_per_point = self.input.pixels_per_point;
272        let take = self.input.take();
273        self.input.pixels_per_point = Some(self.screen_descriptor.pixels_per_point);
274        if let Some(ppp) = pixels_per_point {
275            self.screen_descriptor.pixels_per_point = ppp;
276        }
277        take
278    }
279
280    /// Set start time for egui timer related activity.
281    pub fn start_time(&mut self, elapsed: f64) {
282        self.input.time = Some(elapsed);
283    }
284}
285
286/// Handles input/events from FLTK
287pub fn input_to_egui<W>(win: &mut W, event: enums::Event, state: &mut EguiState)
288where
289    W: WindowExt + PPU,
290{
291    match event {
292        enums::Event::Resize => {
293            state.screen_descriptor.size_in_pixels = [win.width() as _, win.height() as _];
294            state.set_visual_scale(state.pixels_per_point());
295            state._window_resized = true;
296        }
297        //MouseButonLeft pressed is the only one needed by egui
298        enums::Event::Push => {
299            let mouse_btn = match app::event_mouse_button() {
300                app::MouseButton::Left => Some(egui::PointerButton::Primary),
301                app::MouseButton::Middle => Some(egui::PointerButton::Middle),
302                app::MouseButton::Right => Some(egui::PointerButton::Secondary),
303                _ => None,
304            };
305            if let Some(pressed) = mouse_btn {
306                state._mouse_btn_pressed = true;
307                state.input.events.push(egui::Event::PointerButton {
308                    pos: state.pointer_pos,
309                    button: pressed,
310                    pressed: true,
311                    modifiers: state.input.modifiers,
312                });
313            }
314        }
315
316        //MouseButonLeft pressed is the only one needed by egui
317        enums::Event::Released => {
318            // fix unreachable, we can use Option.
319            let mouse_btn = match app::event_mouse_button() {
320                app::MouseButton::Left => Some(egui::PointerButton::Primary),
321                app::MouseButton::Middle => Some(egui::PointerButton::Middle),
322                app::MouseButton::Right => Some(egui::PointerButton::Secondary),
323                _ => None,
324            };
325            if let Some(released) = mouse_btn {
326                state._mouse_btn_pressed = false;
327                state.input.events.push(egui::Event::PointerButton {
328                    pos: state.pointer_pos,
329                    button: released,
330                    pressed: false,
331                    modifiers: state.input.modifiers,
332                });
333            }
334        }
335
336        enums::Event::Move | enums::Event::Drag => {
337            let (x, y) = app::event_coords();
338            let ppp = state.pixels_per_point();
339            state.pointer_pos = pos2(x as f32 / ppp, y as f32 / ppp);
340            state
341                .input
342                .events
343                .push(egui::Event::PointerMoved(state.pointer_pos))
344        }
345
346        enums::Event::KeyUp => {
347            if let Some(key) = translate_virtual_key_code(app::event_key()) {
348                let keymod = app::event_state();
349                state.input.modifiers = egui::Modifiers {
350                    alt: (keymod & enums::EventState::Alt == enums::EventState::Alt),
351                    ctrl: (keymod & enums::EventState::Ctrl == enums::EventState::Ctrl),
352                    shift: (keymod & enums::EventState::Shift == enums::EventState::Shift),
353                    mac_cmd: keymod & enums::EventState::Meta == enums::EventState::Meta,
354
355                    //TOD: Test on both windows and mac
356                    command: (keymod & enums::EventState::Command == enums::EventState::Command),
357                };
358                state.input.events.push(egui::Event::Key {
359                    key,
360                    pressed: false,
361                    modifiers: state.input.modifiers,
362                });
363            }
364        }
365
366        enums::Event::KeyDown => {
367            if let Some(c) = app::event_text().chars().next() {
368                if let Some(del) = app::compose() {
369                    state.input.events.push(egui::Event::Text(c.to_string()));
370                    if del != 0 {
371                        app::compose_reset();
372                    }
373                }
374            }
375            if let Some(key) = translate_virtual_key_code(app::event_key()) {
376                let keymod = app::event_state();
377                state.input.modifiers = egui::Modifiers {
378                    alt: (keymod & enums::EventState::Alt == enums::EventState::Alt),
379                    ctrl: (keymod & enums::EventState::Ctrl == enums::EventState::Ctrl),
380                    shift: (keymod & enums::EventState::Shift == enums::EventState::Shift),
381                    mac_cmd: keymod & enums::EventState::Meta == enums::EventState::Meta,
382
383                    //TOD: Test on both windows and mac
384                    command: (keymod & enums::EventState::Command == enums::EventState::Command),
385                };
386                state.input.events.push(egui::Event::Key {
387                    key,
388                    pressed: true,
389                    modifiers: state.input.modifiers,
390                });
391                if state.input.modifiers.command && key == egui::Key::C {
392                    // println!("copy event");
393                    state.input.events.push(egui::Event::Copy);
394                } else if state.input.modifiers.command && key == egui::Key::X {
395                    // println!("cut event");
396                    state.input.events.push(egui::Event::Cut);
397                } else if state.input.modifiers.command && key == egui::Key::V {
398                    if let Some(value) = state.clipboard.get() {
399                        state.input.events.push(egui::Event::Text(value));
400                    }
401                }
402            }
403        }
404
405        enums::Event::MouseWheel => {
406            if app::is_event_ctrl() {
407                let zoom_factor = state.zoom_factor;
408                match app::event_dy() {
409                    app::MouseWheel::Up => {
410                        let delta = vec2(1., -1.) * zoom_factor;
411
412                        // Treat as zoom in:
413                        state
414                            .input
415                            .events
416                            .push(Event::Zoom((delta.y / 200.0).exp()));
417                    }
418                    app::MouseWheel::Down => {
419                        let delta = vec2(-1., 1.) * zoom_factor;
420
421                        // Treat as zoom out:
422                        state
423                            .input
424                            .events
425                            .push(Event::Zoom((delta.y / 200.0).exp()));
426                    }
427                    _ => (),
428                }
429            } else {
430                let scroll_factor = state.scroll_factor;
431                match app::event_dy() {
432                    app::MouseWheel::Up => {
433                        state.input.events.push(Event::Scroll(Vec2 {
434                            x: 0.,
435                            y: -scroll_factor,
436                        }));
437                    }
438                    app::MouseWheel::Down => {
439                        state.input.events.push(Event::Scroll(Vec2 {
440                            x: 0.,
441                            y: scroll_factor,
442                        }));
443                    }
444                    _ => (),
445                }
446            }
447        }
448
449        _ => {
450            //dbg!(event);
451        }
452    }
453}
454
455/// Translates key codes
456pub fn translate_virtual_key_code(key: enums::Key) -> Option<egui::Key> {
457    match key {
458        enums::Key::Left => Some(egui::Key::ArrowLeft),
459        enums::Key::Up => Some(egui::Key::ArrowUp),
460        enums::Key::Right => Some(egui::Key::ArrowRight),
461        enums::Key::Down => Some(egui::Key::ArrowDown),
462        enums::Key::Escape => Some(egui::Key::Escape),
463        enums::Key::Tab => Some(egui::Key::Tab),
464        enums::Key::BackSpace => Some(egui::Key::Backspace),
465        enums::Key::Insert => Some(egui::Key::Insert),
466        enums::Key::Home => Some(egui::Key::Home),
467        enums::Key::Delete => Some(egui::Key::Delete),
468        enums::Key::End => Some(egui::Key::End),
469        enums::Key::PageDown => Some(egui::Key::PageDown),
470        enums::Key::PageUp => Some(egui::Key::PageUp),
471        enums::Key::Enter => Some(egui::Key::Enter),
472        _ => {
473            if let Some(k) = key.to_char() {
474                match k {
475                    ' ' => Some(egui::Key::Space),
476                    'a' => Some(egui::Key::A),
477                    'b' => Some(egui::Key::B),
478                    'c' => Some(egui::Key::C),
479                    'd' => Some(egui::Key::D),
480                    'e' => Some(egui::Key::E),
481                    'f' => Some(egui::Key::F),
482                    'g' => Some(egui::Key::G),
483                    'h' => Some(egui::Key::H),
484                    'i' => Some(egui::Key::I),
485                    'j' => Some(egui::Key::J),
486                    'k' => Some(egui::Key::K),
487                    'l' => Some(egui::Key::L),
488                    'm' => Some(egui::Key::M),
489                    'n' => Some(egui::Key::N),
490                    'o' => Some(egui::Key::O),
491                    'p' => Some(egui::Key::P),
492                    'q' => Some(egui::Key::Q),
493                    'r' => Some(egui::Key::R),
494                    's' => Some(egui::Key::S),
495                    't' => Some(egui::Key::T),
496                    'u' => Some(egui::Key::U),
497                    'v' => Some(egui::Key::V),
498                    'w' => Some(egui::Key::W),
499                    'x' => Some(egui::Key::X),
500                    'y' => Some(egui::Key::Y),
501                    'z' => Some(egui::Key::Z),
502                    '0' => Some(egui::Key::Num0),
503                    '1' => Some(egui::Key::Num1),
504                    '2' => Some(egui::Key::Num2),
505                    '3' => Some(egui::Key::Num3),
506                    '4' => Some(egui::Key::Num4),
507                    '5' => Some(egui::Key::Num5),
508                    '6' => Some(egui::Key::Num6),
509                    '7' => Some(egui::Key::Num7),
510                    '8' => Some(egui::Key::Num8),
511                    '9' => Some(egui::Key::Num9),
512                    _ => None,
513                }
514            } else {
515                None
516            }
517        }
518    }
519}
520
521/// Translates FLTK cursor to Egui cursors
522pub fn translate_cursor<W>(win: &mut W, fused: &mut FusedCursor, cursor_icon: CursorIcon)
523where
524    W: WindowExt,
525{
526    let tmp_icon = match cursor_icon {
527        CursorIcon::None => enums::Cursor::None,
528        CursorIcon::Default => enums::Cursor::Arrow,
529        CursorIcon::Help => enums::Cursor::Help,
530        CursorIcon::PointingHand => enums::Cursor::Hand,
531        CursorIcon::ResizeHorizontal => enums::Cursor::WE,
532        CursorIcon::ResizeNeSw => enums::Cursor::NESW,
533        CursorIcon::ResizeNwSe => enums::Cursor::NWSE,
534        CursorIcon::ResizeVertical => enums::Cursor::NS,
535        CursorIcon::Text => enums::Cursor::Insert,
536        CursorIcon::Crosshair => enums::Cursor::Cross,
537        CursorIcon::NotAllowed | CursorIcon::NoDrop => enums::Cursor::Wait,
538        CursorIcon::Wait => enums::Cursor::Wait,
539        CursorIcon::Progress => enums::Cursor::Wait,
540        CursorIcon::Grab => enums::Cursor::Hand,
541        CursorIcon::Grabbing => enums::Cursor::Move,
542        CursorIcon::Move => enums::Cursor::Move,
543
544        _ => enums::Cursor::Arrow,
545    };
546
547    if tmp_icon != fused.cursor_icon {
548        fused.cursor_icon = tmp_icon;
549        win.set_cursor(tmp_icon);
550    }
551}
552
553/// Compat for epi::App impl trait
554pub struct Compat {
555    setup: bool,
556}
557
558impl Default for Compat {
559    fn default() -> Self {
560        Self { setup: true }
561    }
562}
563
564impl Compat {
565    /// Called once before the first frame.
566    pub fn needs_setup(&mut self) -> bool {
567        if self.setup {
568            self.setup = false;
569            return true;
570        }
571        self.setup
572    }
573}
574
575pub struct Timer {
576    timer: u32,
577    elapse: u32,
578    duration: f32,
579}
580
581impl Timer {
582    /// Elapse every, approximately in second(s).
583    pub fn new(elapse: u32) -> Self {
584        let _elapse = elapse * 180;
585        let duration = _elapse as f32 / 1000.0;
586        Self {
587            timer: 0,
588            elapse: elapse * 6,
589            duration,
590        }
591    }
592
593    /// Check if the timer is elapsed.
594    pub fn elapsed(&mut self) -> bool {
595        if self.timer >= self.elapse {
596            self.timer = 0;
597            return true;
598        }
599        self.timer += 1;
600        app::sleep(self.duration.into());
601        false
602    }
603}
604
605pub trait EguiImageConvertible<I>
606where
607    I: ImageExt,
608{
609    fn egui_image(self, debug_name: &str) -> Result<RetainedEguiImage, FltkError>;
610}
611
612impl<I> EguiImageConvertible<I> for I
613where
614    I: ImageExt,
615{
616    /// Return (egui_extras::RetainedEguiImage)
617    fn egui_image(self, debug_name: &str) -> Result<RetainedEguiImage, FltkError> {
618        let size = [self.data_w() as _, self.data_h() as _];
619        let color_image = egui::ColorImage::from_rgba_unmultiplied(
620            size,
621            &self
622                .to_rgb()?
623                .convert(enums::ColorDepth::Rgba8)?
624                .to_rgb_data(),
625        );
626
627        Ok(RetainedEguiImage::from_color_image(debug_name, color_image))
628    }
629}
630
631pub trait EguiSvgConvertible {
632    fn egui_svg_image(self, debug_name: &str) -> Result<RetainedEguiImage, FltkError>;
633}
634
635impl EguiSvgConvertible for fltk::image::SvgImage {
636    /// Return (egui_extras::RetainedEguiImage)
637    fn egui_svg_image(mut self, debug_name: &str) -> Result<RetainedEguiImage, FltkError> {
638        self.normalize();
639        let size = [self.data_w() as usize, self.data_h() as usize];
640        let color_image = egui::ColorImage::from_rgba_unmultiplied(
641            size,
642            &self
643                .to_rgb()?
644                .convert(enums::ColorDepth::Rgba8)?
645                .to_rgb_data(),
646        );
647
648        Ok(RetainedEguiImage::from_color_image(debug_name, color_image))
649    }
650}
651/// egui::ColorImage Extender.
652pub trait ColorImageExt {
653    fn from_vec_color32(size: [usize; 2], vec: Vec<egui::Color32>) -> Self;
654
655    fn from_color32_slice(size: [usize; 2], slice: &[egui::Color32]) -> Self;
656}
657
658impl ColorImageExt for egui::ColorImage {
659    fn from_vec_color32(size: [usize; 2], vec: Vec<egui::Color32>) -> Self {
660        let mut pixels: Vec<u8> = Vec::with_capacity(vec.len() * 4);
661        vec.into_iter().for_each(|x| {
662            pixels.push(x[0]);
663            pixels.push(x[1]);
664            pixels.push(x[2]);
665            pixels.push(x[3]);
666        });
667        egui::ColorImage::from_rgba_unmultiplied(size, &pixels)
668    }
669
670    fn from_color32_slice(size: [usize; 2], slice: &[egui::Color32]) -> Self {
671        let mut pixels: Vec<u8> = Vec::with_capacity(slice.len() * 4);
672        slice.into_iter().for_each(|x| {
673            pixels.push(x[0]);
674            pixels.push(x[1]);
675            pixels.push(x[2]);
676            pixels.push(x[3]);
677        });
678        egui::ColorImage::from_rgba_unmultiplied(size, &pixels)
679    }
680}
681
682/// egui::TextureHandle Extender.
683pub trait TextureHandleExt {
684    /// egui::TextureHandle from Vec u8
685    fn from_vec_u8(
686        ctx: &egui::Context,
687        debug_name: &str,
688        size: [usize; 2],
689        vec: Vec<u8>,
690    ) -> egui::TextureHandle;
691
692    fn from_u8_slice(
693        ctx: &egui::Context,
694        debug_name: &str,
695        size: [usize; 2],
696        slice: &[u8],
697    ) -> egui::TextureHandle;
698
699    fn from_vec_color32(
700        ctx: &egui::Context,
701        debug_name: &str,
702        size: [usize; 2],
703        vec: Vec<egui::Color32>,
704    ) -> egui::TextureHandle;
705
706    fn from_color32_slice(
707        ctx: &egui::Context,
708        debug_name: &str,
709        size: [usize; 2],
710        slice: &[egui::Color32],
711    ) -> egui::TextureHandle;
712}
713
714impl TextureHandleExt for egui::TextureHandle {
715    fn from_vec_u8(ctx: &egui::Context, debug_name: &str, size: [usize; 2], vec: Vec<u8>) -> Self {
716        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, &vec);
717        drop(vec);
718        ctx.load_texture(debug_name, color_image, egui::TextureFilter::Linear)
719    }
720
721    fn from_u8_slice(
722        ctx: &egui::Context,
723        debug_name: &str,
724        size: [usize; 2],
725        slice: &[u8],
726    ) -> Self {
727        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, slice);
728        ctx.load_texture(debug_name, color_image, egui::TextureFilter::Linear)
729    }
730
731    fn from_vec_color32(
732        ctx: &egui::Context,
733        debug_name: &str,
734        size: [usize; 2],
735        vec: Vec<egui::Color32>,
736    ) -> Self {
737        let mut pixels: Vec<u8> = Vec::with_capacity(vec.len() * 4);
738        vec.into_iter().for_each(|x| {
739            pixels.push(x[0]);
740            pixels.push(x[1]);
741            pixels.push(x[2]);
742            pixels.push(x[3]);
743        });
744        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
745        ctx.load_texture(debug_name, color_image, egui::TextureFilter::Linear)
746    }
747
748    fn from_color32_slice(
749        ctx: &egui::Context,
750        debug_name: &str,
751        size: [usize; 2],
752        slice: &[egui::Color32],
753    ) -> Self {
754        let mut pixels: Vec<u8> = Vec::with_capacity(slice.len() * 4);
755        slice.into_iter().for_each(|x| {
756            pixels.push(x[0]);
757            pixels.push(x[1]);
758            pixels.push(x[2]);
759            pixels.push(x[3]);
760        });
761        let color_image = egui::ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
762        ctx.load_texture(debug_name, color_image, egui::TextureFilter::Linear)
763    }
764}
765
766/// Compat trait ext for RawWindowHandle 4.x
767///
768pub trait RWHandleExt {
769    /// use raw-window-handle 4.x compatible
770    fn use_compat(&self) -> RwhCompat;
771}
772
773impl RWHandleExt for fltk::window::Window {
774    fn use_compat(&self) -> RwhCompat {
775        RwhCompat(self.raw_handle())
776    }
777}
778
779#[cfg(feature = "enable-glwindow")]
780impl RWHandleExt for fltk::window::GlWindow {
781    fn use_compat(&self) -> RwhCompat {
782        RwhCompat(self.raw_handle())
783    }
784}
785
786/// Compat for RawWindowHandle 4.x
787///
788pub struct RwhCompat(fltk::window::RawHandle);
789
790unsafe impl raw_window_handle::HasRawWindowHandle for RwhCompat {
791    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
792        #[cfg(target_os = "windows")]
793        {
794            let mut handle = raw_window_handle::Win32Handle::empty();
795            handle.hwnd = self.0;
796            handle.hinstance = fltk::app::display();
797            return raw_window_handle::RawWindowHandle::Win32(handle);
798        }
799
800        #[cfg(target_os = "macos")]
801        {
802            use std::os::raw::c_void;
803
804            let raw = self.0;
805            extern "C" {
806                pub fn cfltk_getContentView(xid: *mut c_void) -> *mut c_void;
807            }
808            let cv = unsafe { cfltk_getContentView(raw) };
809            let mut handle = raw_window_handle::AppKitHandle::empty();
810            handle.ns_window = raw;
811            handle.ns_view = cv as _;
812            return raw_window_handle::RawWindowHandle::AppKit(handle);
813        }
814
815        #[cfg(target_os = "android")]
816        {
817            let mut handle = raw_window_handle::AndroidNdkHandle::empty();
818            handle.a_native_window = self.0;
819            return raw_window_handle::RawWindowHandle::AndroidNdk(handle);
820        }
821
822        #[cfg(any(
823            target_os = "linux",
824            target_os = "dragonfly",
825            target_os = "freebsd",
826            target_os = "netbsd",
827            target_os = "openbsd",
828        ))]
829        {
830            #[cfg(not(feature = "wayland"))]
831            {
832                let mut handle = raw_window_handle::XlibHandle::empty();
833                handle.window = self.0;
834                handle.display = fltk::app::display();
835                return raw_window_handle::RawWindowHandle::Xlib(handle);
836            }
837
838            #[cfg(feature = "wayland")]
839            {
840                let mut handle = raw_window_handle::WaylandHandle::empty();
841                handle.surface = self.0;
842                handle.display = fltk::app::display();
843                return raw_window_handle::RawWindowHandle::Wayland(handle);
844            }
845        }
846    }
847}