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
19pub 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
30pub fn get_frame_time(start_time: Instant) -> f32 {
32 (Instant::now() - start_time).as_secs_f64() as f32
33}
34
35pub 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
42pub struct FusedCursor {
44 pub cursor_icon: fltk::enums::Cursor,
45}
46
47const ARROW: enums::Cursor = enums::Cursor::Arrow;
48
49impl FusedCursor {
50 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
62pub struct EguiState {
64 pub canvas_size: [u32; 2],
65 pub clipboard: Clipboard,
66 pub fuse_cursor: FusedCursor,
67 pub input: RawInput,
69 _pixels_per_point: f32,
70 pub pointer_pos: Pos2,
71 pub scroll_factor: f32,
73 pub zoom_factor: f32,
75 _window_resized: bool,
77 pub max_texture_side: usize,
78}
79
80impl EguiState {
81 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 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 take = self.input.take();
110 take
115 }
116
117 pub fn pixels_per_point(&self) -> f32 {
118 self._pixels_per_point
119 }
120
121 pub fn window_resized(&mut self) -> bool {
123 let tmp = self._window_resized;
124 self._window_resized = false;
125 tmp
126 }
127
128 pub fn fuse_input(&mut self, win: &mut GlWindow, event: enums::Event) {
130 input_to_egui(win, event, self);
131 }
132
133 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 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 pub fn set_visual_scale(&mut self, size: f32) {
157 self._pixels_per_point = size;
160
161 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
168pub fn input_to_egui(
170 win: &mut GlWindow,
171 event: enums::Event,
172 state: &mut EguiState,
173 ) {
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 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 enums::Event::Released => {
202 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 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 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 state.input.events.push(Event::Copy)
281 } else if state.input.modifiers.command && key == Key::X {
282 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 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 }
326 }
327}
328
329pub 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
395pub 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 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 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
499pub 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
530pub trait TextureHandleExt {
532 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}