1use ark::applet::input;
2use ark::render;
3use ark::render::TextureFormat;
4use ark::ColorRgba8;
5use ark::Vec2;
6use std::collections::HashMap;
7
8#[derive(Default)]
27pub struct EguiArk {
28 input_mngr: input::InputManager,
29 egui_input: egui::RawInput,
30 ctx: egui::Context,
31 painter: EguiPainter,
32}
33
34impl EguiArk {
35 pub fn ctx(&self) -> &egui::Context {
37 &self.ctx
38 }
39
40 pub fn begin_frame(&mut self, applet: &ark::applet::Applet) -> egui::Context {
42 puffin::profile_function!();
43 if applet.window_state().is_some() {
44 self.input_mngr.update(applet);
45 update_input(&mut self.egui_input, applet, &self.input_mngr);
46 }
47 puffin::profile_scope!("egui::begin_frame");
48 self.ctx.begin_frame(self.egui_input.take());
49 self.ctx.clone()
50 }
51
52 pub fn end_frame(&mut self, render: &render::Render, applet: &ark::applet::Applet) {
54 puffin::profile_function!();
55
56 let full_output = {
57 puffin::profile_scope!("egui::end_frame");
58 self.ctx.end_frame()
59 };
60
61 let platform_output = full_output.platform_output;
62
63 if applet.window_state().is_some() {
64 let paint_jobs = {
65 puffin::profile_scope!("egui::tessellate");
66 self.ctx.tessellate(full_output.shapes)
67 };
68 if !platform_output.copied_text.is_empty() {
69 applet.set_clipboard_string(&platform_output.copied_text);
70 }
71
72 if platform_output.cursor_icon == egui::CursorIcon::None {
73 applet.set_cursor_mode(ark::applet::CursorMode::Hide);
74 } else {
75 applet.set_cursor_mode(ark::applet::CursorMode::None);
76 applet.set_cursor_shape(from_egui_cursor(platform_output.cursor_icon));
77 }
78
79 self.painter
80 .upload_font_textures(render, full_output.textures_delta);
81 self.painter.paint(render, &self.ctx, &paint_jobs);
82 }
83 }
84
85 pub fn wants_pointer_input(&self) -> bool {
91 self.ctx.wants_pointer_input()
92 }
93
94 pub fn wants_keyboard_input(&self) -> bool {
96 self.ctx.wants_keyboard_input()
97 }
98
99 pub fn is_mouse_down(&self) -> bool {
100 self.ctx.input().pointer.any_down()
101 }
102
103 pub fn persist(&self) -> serde_json::Value {
105 serde_json::to_value(&*self.ctx.memory()).unwrap_or_default()
106 }
107
108 pub fn restore(&self, value: serde_json::Value) {
110 match serde_json::from_value(value) {
111 Ok(memory) => {
112 *self.ctx.memory() = memory;
113 }
114 Err(err) => {
115 ark::warn!("Failed to restore egui state: {}", err);
116 }
117 }
118 }
119
120 pub fn viewport(&self) -> ark::render::Rectangle {
127 let available_rect = self.ctx.available_rect();
128 let pixels_per_point = self.ctx.input().pixels_per_point();
129 ark::render::Rectangle {
130 min_x: available_rect.min.x * pixels_per_point,
131 min_y: available_rect.min.y * pixels_per_point,
132 max_x: available_rect.max.x * pixels_per_point,
133 max_y: available_rect.max.y * pixels_per_point,
134 }
135 }
136}
137
138fn from_egui_cursor(cursor: egui::CursorIcon) -> ark::applet::CursorShape {
139 use ark::applet::CursorShape;
140 match cursor {
141 egui::CursorIcon::None => unreachable!("Should have been handled outside this function"),
142 egui::CursorIcon::Default => CursorShape::Default,
143 egui::CursorIcon::ContextMenu => CursorShape::ContextMenu,
144 egui::CursorIcon::Help => CursorShape::Help,
145 egui::CursorIcon::PointingHand => CursorShape::Hand,
146 egui::CursorIcon::Progress => CursorShape::Progress,
147 egui::CursorIcon::Wait => CursorShape::Wait,
148 egui::CursorIcon::Cell => CursorShape::Cell,
149 egui::CursorIcon::Crosshair => CursorShape::Crosshair,
150 egui::CursorIcon::Text => CursorShape::Text,
151 egui::CursorIcon::VerticalText => CursorShape::VerticalText,
152 egui::CursorIcon::Alias => CursorShape::Alias,
153 egui::CursorIcon::Copy => CursorShape::Copy,
154 egui::CursorIcon::Move => CursorShape::Move,
155 egui::CursorIcon::NoDrop => CursorShape::NoDrop,
156 egui::CursorIcon::NotAllowed => CursorShape::NotAllowed,
157 egui::CursorIcon::Grab => CursorShape::Grab,
158 egui::CursorIcon::Grabbing => CursorShape::Grabbing,
159 egui::CursorIcon::AllScroll => CursorShape::AllScroll,
160 egui::CursorIcon::ResizeHorizontal => CursorShape::EWResize,
161 egui::CursorIcon::ResizeNeSw => CursorShape::NESWResize,
162 egui::CursorIcon::ResizeNwSe => CursorShape::NWSEResize,
163 egui::CursorIcon::ResizeVertical => CursorShape::NSResize,
164 egui::CursorIcon::ZoomIn => CursorShape::ZoomIn,
165 egui::CursorIcon::ZoomOut => CursorShape::ZoomOut,
166 egui::CursorIcon::ResizeEast => CursorShape::EResize,
167 egui::CursorIcon::ResizeSouthEast => CursorShape::SEResize,
168 egui::CursorIcon::ResizeSouth => CursorShape::SResize,
169 egui::CursorIcon::ResizeSouthWest => CursorShape::SWResize,
170 egui::CursorIcon::ResizeWest => CursorShape::WResize,
171 egui::CursorIcon::ResizeNorthWest => CursorShape::NWResize,
172 egui::CursorIcon::ResizeNorth => CursorShape::NResize,
173 egui::CursorIcon::ResizeNorthEast => CursorShape::NEResize,
174 egui::CursorIcon::ResizeColumn => CursorShape::RowResize,
175 egui::CursorIcon::ResizeRow => CursorShape::ColResize,
176 }
177}
178
179impl serde::Serialize for EguiArk {
182 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183 where
184 S: serde::Serializer,
185 {
186 self.persist().serialize(serializer)
189 }
190}
191
192impl<'de> serde::Deserialize<'de> for EguiArk {
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: serde::Deserializer<'de>,
196 {
197 let egui_ark = Self::default();
198
199 egui_ark.restore(serde_json::Value::deserialize(deserializer)?);
202
203 Ok(egui_ark)
204 }
205}
206
207#[derive(Default)]
210struct EguiPainter {
211 egui_textures: HashMap<egui::TextureId, render::Texture>,
213
214 positions: Vec<Vec2>,
216 colors: Vec<ColorRgba8>,
217 uvs: Vec<Vec2>,
218}
219
220impl EguiPainter {
221 pub fn upload_font_textures(
222 &mut self,
223 render: &render::Render,
224 textures_delta: egui::TexturesDelta,
225 ) {
226 puffin::profile_function!();
227 for (texture_id, image_delta) in textures_delta.set {
228 if let Some(pos) = image_delta.pos {
229 if let Some(texture) = self.egui_textures.get_mut(&texture_id) {
231 let pixels = get_rgba_pixels(&image_delta.image);
233 texture.update_rectangle(
234 pos[0] as u32,
235 pos[1] as u32,
236 image_delta.image.width() as u32,
237 image_delta.image.height() as u32,
238 &pixels,
239 );
240 }
241 } else {
242 self.egui_textures
243 .insert(texture_id, as_ark_texture(render, &image_delta));
244 }
245 }
246
247 for free in textures_delta.free {
248 self.egui_textures.remove(&free);
249 }
250 }
251
252 pub fn paint(
253 &mut self,
254 render: &render::Render,
255 egui_ctx: &egui::Context,
256 meshes: &[egui::ClippedPrimitive],
257 ) {
258 puffin::profile_function!();
259 let dpi_factor = egui_ctx.input().pixels_per_point();
260 for egui::ClippedPrimitive {
261 primitive,
262 clip_rect,
263 } in meshes
264 {
265 self.paint_mesh(render, dpi_factor, clip_rect, primitive);
266 }
267 }
268
269 fn paint_mesh(
270 &mut self,
271 render: &render::Render,
272 dpi_factor: f32,
273 clip_rect: &egui::Rect,
274 primitive: &egui::epaint::Primitive,
275 ) {
276 puffin::profile_function!();
277
278 let mesh = match primitive {
279 egui::epaint::Primitive::Mesh(mesh) => mesh,
280 _ => return,
281 };
282
283 if !mesh.is_valid() {
284 ark::error!("egui generated an invalid triangle mesh");
285 return;
286 }
287
288 self.positions.clear();
289 self.colors.clear();
290 self.uvs.clear();
291 for v in &mesh.vertices {
292 self.positions
293 .push(dpi_factor * Vec2::new(v.pos.x, v.pos.y));
294 self.colors.push(ColorRgba8(v.color.to_array()));
295 self.uvs.push(Vec2::new(v.uv.x, v.uv.y));
296 }
297
298 let clip_rect = render::Rectangle {
299 min_x: dpi_factor * clip_rect.min.x,
300 min_y: dpi_factor * clip_rect.min.y,
301 max_x: dpi_factor * clip_rect.max.x,
302 max_y: dpi_factor * clip_rect.max.y,
303 };
304
305 let texture_handle = if let Some(texture) = self.egui_textures.get(&mesh.texture_id) {
306 texture.handle()
307 } else {
308 return;
309 };
310
311 let indices: &[u32] = &mesh.indices;
312 let indices: &[[u32; 3]] = unsafe { transmute_slice(indices) };
313 assert_eq!(mesh.indices.len(), 3 * indices.len());
314
315 puffin::profile_scope!("draw_textured_triangles");
316 render.draw_textured_triangles(
317 &clip_rect,
318 texture_handle,
319 indices,
320 &self.positions,
321 &self.colors,
322 &self.uvs,
323 );
324 }
325}
326
327#[allow(clippy::integer_division)]
329unsafe fn transmute_slice<Target: Copy, Source: Copy>(source: &[Source]) -> &[Target] {
330 use std::mem::size_of;
331
332 let target_len = source.len() * size_of::<Source>() / size_of::<Target>();
333
334 assert_eq!(
335 target_len * size_of::<Target>(),
336 source.len() * size_of::<Source>(),
337 "Source slice length is not an even multiple of the target"
338 );
339 unsafe { std::slice::from_raw_parts(source.as_ptr().cast::<Target>(), target_len) }
340}
341
342fn get_rgba_pixels(image: &egui::epaint::ImageData) -> Vec<u8> {
343 let mut pixels = Vec::with_capacity(image.width() * image.height() * 4);
344 match image {
345 egui::epaint::ImageData::Font(font) => {
346 for srgba in font.srgba_pixels(1.0) {
347 pixels.push(srgba[0]);
348 pixels.push(srgba[1]);
349 pixels.push(srgba[2]);
350 pixels.push(srgba[3]);
351 }
352 }
353 _ => {
354 pixels.shrink_to_fit();
355 }
356 }
357
358 pixels
359}
360
361fn as_ark_texture(
362 render: &render::Render,
363 image_delta: &egui::epaint::ImageDelta,
364) -> render::Texture {
365 let pixels = get_rgba_pixels(&image_delta.image);
366
367 puffin::profile_scope!("render::Texture::create");
368 render
369 .create_texture()
370 .name("egui")
371 .dimensions(image_delta.image.width(), image_delta.image.height())
372 .format(TextureFormat::R8G8B8A8_UNORM)
373 .data(&pixels)
374 .build()
375 .expect("Failed to create egui texture")
376}
377
378fn update_input(
380 egui_input: &mut egui::RawInput,
381 applet: &ark::applet::Applet,
382 input_mngr: &input::InputManager,
383) {
384 puffin::profile_function!();
385 let window_state = if let Some(window_state) = applet.window_state() {
386 window_state
387 } else {
388 return;
389 };
390
391 egui_input.screen_rect = Some(egui::Rect::from_min_size(
392 Default::default(),
393 egui::vec2(window_state.width, window_state.height),
394 ));
395 egui_input.pixels_per_point = Some(window_state.dpi_factor);
396 egui_input.time = Some(applet.real_time_since_start());
397 egui_input.events.clear();
398 egui_input.modifiers = as_egui_modifiers(&input_mngr.state().modifiers);
399
400 for (state, event) in input_mngr.events() {
401 match event {
402 input::Event::Key { key, pressed } => {
403 if let Some(key) = as_egui_key(*key) {
404 egui_input.events.push(egui::Event::Key {
405 pressed: *pressed,
406 key,
407 modifiers: as_egui_modifiers(&state.modifiers),
408 });
409 }
410 }
411 input::Event::Char(chr) => {
412 if *chr != '\r' {
413 egui_input.events.push(egui::Event::Text(chr.to_string()));
414 }
415 }
416
417 input::Event::PointerMove { pos, primary, .. } => {
418 if *primary {
419 egui_input
420 .events
421 .push(egui::Event::PointerMoved(egui::pos2(pos.x, pos.y)));
422 }
423 }
424 input::Event::PointerButton {
425 pressed,
426 button,
427 pos,
428 ..
429 } => {
430 egui_input.events.push(egui::Event::PointerButton {
431 pos: egui::pos2(pos.x, pos.y),
432 button: as_egui_button(button),
433 pressed: *pressed,
434 modifiers: as_egui_modifiers(&state.modifiers),
435 });
436 }
437 input::Event::PointerDelta { .. } => {}
438 input::Event::Scroll { delta, .. } => {
439 egui_input
440 .events
441 .push(egui::Event::Scroll(egui::vec2(delta.x, delta.y)));
442 }
443 input::Event::Command(command) => {
444 match command {
445 input::Command::Copy => egui_input.events.push(egui::Event::Copy),
446 input::Command::Cut => egui_input.events.push(egui::Event::Cut),
447 input::Command::Paste => {
448 if let Some(s) = applet.clipboard_string() {
449 egui_input.events.push(egui::Event::Text(s));
450 }
451 }
452 input::Command::Undo | input::Command::Redo | input::Command::Save |
454 input::Command::New => {}
456 }
457 }
458 input::Event::Axis { .. }
459 | input::Event::GamepadButton { .. }
460 | input::Event::RawMidi { .. } => {}
461 }
462 }
463}
464
465fn as_egui_key(code: input::Key) -> Option<egui::Key> {
466 match code {
467 input::Key::Down => Some(egui::Key::ArrowDown),
468 input::Key::Left => Some(egui::Key::ArrowLeft),
469 input::Key::Right => Some(egui::Key::ArrowRight),
470 input::Key::Up => Some(egui::Key::ArrowUp),
471
472 input::Key::Escape => Some(egui::Key::Escape),
473 input::Key::Tab => Some(egui::Key::Tab),
474 input::Key::Back => Some(egui::Key::Backspace),
475 input::Key::Return => Some(egui::Key::Enter),
476 input::Key::Space => Some(egui::Key::Space),
477
478 input::Key::Insert => Some(egui::Key::Insert),
479 input::Key::Delete => Some(egui::Key::Delete),
480 input::Key::Home => Some(egui::Key::Home),
481 input::Key::End => Some(egui::Key::End),
482 input::Key::PageUp => Some(egui::Key::PageUp),
483 input::Key::PageDown => Some(egui::Key::PageDown),
484
485 input::Key::Key0 => Some(egui::Key::Num0),
486 input::Key::Key1 => Some(egui::Key::Num1),
487 input::Key::Key2 => Some(egui::Key::Num2),
488 input::Key::Key3 => Some(egui::Key::Num3),
489 input::Key::Key4 => Some(egui::Key::Num4),
490 input::Key::Key5 => Some(egui::Key::Num5),
491 input::Key::Key6 => Some(egui::Key::Num6),
492 input::Key::Key7 => Some(egui::Key::Num7),
493 input::Key::Key8 => Some(egui::Key::Num8),
494 input::Key::Key9 => Some(egui::Key::Num9),
495
496 input::Key::A => Some(egui::Key::A),
497 input::Key::B => Some(egui::Key::B),
498 input::Key::C => Some(egui::Key::C),
499 input::Key::D => Some(egui::Key::D),
500 input::Key::E => Some(egui::Key::E),
501 input::Key::F => Some(egui::Key::F),
502 input::Key::G => Some(egui::Key::G),
503 input::Key::H => Some(egui::Key::H),
504 input::Key::I => Some(egui::Key::I),
505 input::Key::J => Some(egui::Key::J),
506 input::Key::K => Some(egui::Key::K),
507 input::Key::L => Some(egui::Key::L),
508 input::Key::M => Some(egui::Key::M),
509 input::Key::N => Some(egui::Key::N),
510 input::Key::O => Some(egui::Key::O),
511 input::Key::P => Some(egui::Key::P),
512 input::Key::Q => Some(egui::Key::Q),
513 input::Key::R => Some(egui::Key::R),
514 input::Key::S => Some(egui::Key::S),
515 input::Key::T => Some(egui::Key::T),
516 input::Key::U => Some(egui::Key::U),
517 input::Key::V => Some(egui::Key::V),
518 input::Key::W => Some(egui::Key::W),
519 input::Key::X => Some(egui::Key::X),
520 input::Key::Y => Some(egui::Key::Y),
521 input::Key::Z => Some(egui::Key::Z),
522
523 _ => None,
524 }
525}
526
527fn as_egui_modifiers(modifiers: &input::Modifiers) -> egui::Modifiers {
528 egui::Modifiers {
529 alt: modifiers.alt,
530 ctrl: modifiers.ctrl,
531 shift: modifiers.shift,
532 mac_cmd: modifiers.cmd, command: modifiers.cmd,
534 }
535}
536
537fn as_egui_button(button: &input::PointerButton) -> egui::PointerButton {
538 match button {
539 input::PointerButton::Primary => egui::PointerButton::Primary,
540 input::PointerButton::Secondary => egui::PointerButton::Secondary,
541 input::PointerButton::Middle => egui::PointerButton::Middle,
542 }
543}