1#![warn(missing_docs)]
121#![allow(clippy::tabs_in_doc_comments)]
122
123pub use egui;
124
125use std::{fmt::Display, sync::Arc, time::Instant};
126
127use copypasta::{ClipboardContext, ClipboardProvider};
128use egui::{ClippedMesh, CtxRef, RawInput};
129use tetra::{
130 graphics::{self, BlendAlphaMode, BlendMode},
131 Event, TetraError,
132};
133
134const SCROLL_SENSITIVITY: f32 = 48.0;
135const ZOOM_SENSITIVITY: f32 = 1.25;
136
137fn tetra_vec2_to_egui_pos2(tetra_vec2: tetra::math::Vec2<f32>) -> egui::Pos2 {
138 egui::pos2(tetra_vec2.x, tetra_vec2.y)
139}
140
141fn egui_pos2_to_tetra_vec2(egui_pos2: egui::Pos2) -> tetra::math::Vec2<f32> {
142 tetra::math::Vec2::new(egui_pos2.x, egui_pos2.y)
143}
144
145fn egui_rect_to_tetra_rectangle(egui_rect: egui::Rect) -> tetra::graphics::Rectangle<i32> {
146 tetra::graphics::Rectangle::new(
147 egui_rect.left() as i32,
148 egui_rect.top() as i32,
149 egui_rect.width() as i32,
150 egui_rect.height() as i32,
151 )
152}
153
154fn egui_color32_to_tetra_color(egui_color: egui::Color32) -> tetra::graphics::Color {
155 tetra::graphics::Color::rgba8(
156 egui_color.r(),
157 egui_color.g(),
158 egui_color.b(),
159 egui_color.a(),
160 )
161}
162
163fn tetra_key_to_egui_key(key: tetra::input::Key) -> Option<egui::Key> {
170 match key {
171 tetra::input::Key::A => Some(egui::Key::A),
172 tetra::input::Key::B => Some(egui::Key::B),
173 tetra::input::Key::C => Some(egui::Key::C),
174 tetra::input::Key::D => Some(egui::Key::D),
175 tetra::input::Key::E => Some(egui::Key::E),
176 tetra::input::Key::F => Some(egui::Key::F),
177 tetra::input::Key::G => Some(egui::Key::G),
178 tetra::input::Key::H => Some(egui::Key::H),
179 tetra::input::Key::I => Some(egui::Key::I),
180 tetra::input::Key::J => Some(egui::Key::J),
181 tetra::input::Key::K => Some(egui::Key::K),
182 tetra::input::Key::L => Some(egui::Key::L),
183 tetra::input::Key::M => Some(egui::Key::M),
184 tetra::input::Key::N => Some(egui::Key::N),
185 tetra::input::Key::O => Some(egui::Key::O),
186 tetra::input::Key::P => Some(egui::Key::P),
187 tetra::input::Key::Q => Some(egui::Key::Q),
188 tetra::input::Key::R => Some(egui::Key::R),
189 tetra::input::Key::S => Some(egui::Key::S),
190 tetra::input::Key::T => Some(egui::Key::T),
191 tetra::input::Key::U => Some(egui::Key::U),
192 tetra::input::Key::V => Some(egui::Key::V),
193 tetra::input::Key::W => Some(egui::Key::W),
194 tetra::input::Key::X => Some(egui::Key::X),
195 tetra::input::Key::Y => Some(egui::Key::Y),
196 tetra::input::Key::Z => Some(egui::Key::Z),
197 tetra::input::Key::Num0 => Some(egui::Key::Num0),
198 tetra::input::Key::Num1 => Some(egui::Key::Num1),
199 tetra::input::Key::Num2 => Some(egui::Key::Num2),
200 tetra::input::Key::Num3 => Some(egui::Key::Num3),
201 tetra::input::Key::Num4 => Some(egui::Key::Num4),
202 tetra::input::Key::Num5 => Some(egui::Key::Num5),
203 tetra::input::Key::Num6 => Some(egui::Key::Num6),
204 tetra::input::Key::Num7 => Some(egui::Key::Num7),
205 tetra::input::Key::Num8 => Some(egui::Key::Num8),
206 tetra::input::Key::Num9 => Some(egui::Key::Num9),
207 tetra::input::Key::NumPad0 => Some(egui::Key::Num0),
208 tetra::input::Key::NumPad1 => Some(egui::Key::Num1),
209 tetra::input::Key::NumPad2 => Some(egui::Key::Num2),
210 tetra::input::Key::NumPad3 => Some(egui::Key::Num3),
211 tetra::input::Key::NumPad4 => Some(egui::Key::Num4),
212 tetra::input::Key::NumPad5 => Some(egui::Key::Num5),
213 tetra::input::Key::NumPad6 => Some(egui::Key::Num6),
214 tetra::input::Key::NumPad7 => Some(egui::Key::Num7),
215 tetra::input::Key::NumPad8 => Some(egui::Key::Num8),
216 tetra::input::Key::NumPad9 => Some(egui::Key::Num9),
217 tetra::input::Key::NumPadEnter => Some(egui::Key::Enter),
218 tetra::input::Key::Up => Some(egui::Key::ArrowUp),
219 tetra::input::Key::Down => Some(egui::Key::ArrowDown),
220 tetra::input::Key::Left => Some(egui::Key::ArrowLeft),
221 tetra::input::Key::Right => Some(egui::Key::ArrowRight),
222 tetra::input::Key::Backspace => Some(egui::Key::Backspace),
223 tetra::input::Key::Delete => Some(egui::Key::Delete),
224 tetra::input::Key::End => Some(egui::Key::End),
225 tetra::input::Key::Enter => Some(egui::Key::Enter),
226 tetra::input::Key::Escape => Some(egui::Key::Escape),
227 tetra::input::Key::Home => Some(egui::Key::Home),
228 tetra::input::Key::Insert => Some(egui::Key::Insert),
229 tetra::input::Key::PageDown => Some(egui::Key::PageDown),
230 tetra::input::Key::PageUp => Some(egui::Key::PageUp),
231 tetra::input::Key::Space => Some(egui::Key::Space),
232 tetra::input::Key::Tab => Some(egui::Key::Tab),
233 _ => None,
234 }
235}
236
237fn tetra_mouse_button_to_egui_pointer_button(
243 tetra_mouse_button: tetra::input::MouseButton,
244) -> Option<egui::PointerButton> {
245 match tetra_mouse_button {
246 tetra::input::MouseButton::Left => Some(egui::PointerButton::Primary),
247 tetra::input::MouseButton::Middle => Some(egui::PointerButton::Middle),
248 tetra::input::MouseButton::Right => Some(egui::PointerButton::Secondary),
249 _ => None,
250 }
251}
252
253fn egui_mesh_to_tetra_mesh(
254 ctx: &mut tetra::Context,
255 egui_mesh: egui::epaint::Mesh,
256 texture: tetra::graphics::Texture,
257) -> tetra::Result<tetra::graphics::mesh::Mesh> {
258 let index_buffer = tetra::graphics::mesh::IndexBuffer::new(ctx, &egui_mesh.indices)?;
259 let vertices: Vec<tetra::graphics::mesh::Vertex> = egui_mesh
260 .vertices
261 .iter()
262 .map(|vertex| {
263 tetra::graphics::mesh::Vertex::new(
264 egui_pos2_to_tetra_vec2(vertex.pos),
265 egui_pos2_to_tetra_vec2(vertex.uv),
266 egui_color32_to_tetra_color(vertex.color),
267 )
268 })
269 .collect();
270 let vertex_buffer = tetra::graphics::mesh::VertexBuffer::new(ctx, &vertices)?;
271 let mut mesh = tetra::graphics::mesh::Mesh::indexed(vertex_buffer, index_buffer);
272 mesh.set_texture(texture);
273 mesh.set_backface_culling(false);
274 Ok(mesh)
275}
276
277fn egui_font_image_to_tetra_texture(
280 ctx: &mut tetra::Context,
281 egui_font_image: Arc<egui::FontImage>,
282) -> tetra::Result<tetra::graphics::Texture> {
283 let mut pixels = vec![];
284 for alpha in &egui_font_image.pixels {
289 pixels.push(*alpha);
290 pixels.push(*alpha);
291 pixels.push(*alpha);
292 pixels.push(*alpha);
293 }
294 tetra::graphics::Texture::from_rgba(
295 ctx,
296 egui_font_image.width as i32,
297 egui_font_image.height as i32,
298 &pixels,
299 )
300}
301
302#[derive(Debug)]
304pub enum Error {
305 TetraError(TetraError),
307 OpenError(std::io::Error),
310 ClipboardError(Box<dyn std::error::Error + Send + Sync>),
312}
313
314impl Display for Error {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 match self {
317 Error::TetraError(error) => error.fmt(f),
318 Error::OpenError(error) => error.fmt(f),
319 Error::ClipboardError(error) => error.fmt(f),
320 }
321 }
322}
323
324impl std::error::Error for Error {
325 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
326 match self {
327 Error::TetraError(error) => Some(error),
328 Error::OpenError(error) => Some(error),
329 Error::ClipboardError(error) => Some(error.as_ref()),
330 }
331 }
332}
333
334impl From<TetraError> for Error {
335 fn from(error: TetraError) -> Self {
336 Self::TetraError(error)
337 }
338}
339
340impl From<std::io::Error> for Error {
341 fn from(v: std::io::Error) -> Self {
342 Self::OpenError(v)
343 }
344}
345
346impl From<Box<dyn std::error::Error + Send + Sync>> for Error {
347 fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
348 Self::ClipboardError(error)
349 }
350}
351
352pub struct EguiWrapper {
355 raw_input: RawInput,
356 ctx: CtxRef,
357 texture: Option<tetra::graphics::Texture>,
358 last_frame_time: Instant,
359 meshes: Vec<(tetra::graphics::Rectangle<i32>, tetra::graphics::mesh::Mesh)>,
360}
361
362impl EguiWrapper {
363 pub fn new() -> Self {
365 Self {
366 raw_input: RawInput::default(),
367 ctx: CtxRef::default(),
368 texture: None,
369 last_frame_time: Instant::now(),
370 meshes: vec![],
371 }
372 }
373
374 pub fn ctx(&self) -> &egui::CtxRef {
376 &self.ctx
377 }
378
379 pub fn event(&mut self, ctx: &tetra::Context, event: &tetra::Event) -> Result<(), Error> {
381 match event {
382 tetra::Event::KeyPressed { key } => {
383 match key {
385 tetra::input::Key::LeftCtrl | tetra::input::Key::RightCtrl => {
386 self.raw_input.modifiers.ctrl = true;
387 self.raw_input.modifiers.command = true;
388 }
389 tetra::input::Key::LeftShift | tetra::input::Key::RightShift => {
390 self.raw_input.modifiers.shift = true;
391 }
392 tetra::input::Key::LeftAlt | tetra::input::Key::RightAlt => {
393 self.raw_input.modifiers.alt = true;
394 }
395 _ => {}
396 }
397
398 if tetra::input::is_key_down(ctx, tetra::input::Key::LeftCtrl)
400 | tetra::input::is_key_down(ctx, tetra::input::Key::RightCtrl)
401 {
402 if let tetra::input::Key::C = key {
403 self.raw_input.events.push(egui::Event::Copy);
404 }
405 if let tetra::input::Key::X = key {
406 self.raw_input.events.push(egui::Event::Cut);
407 }
408 if let tetra::input::Key::V = key {
409 self.raw_input
410 .events
411 .push(egui::Event::Text(ClipboardContext::new()?.get_contents()?));
412 }
413 }
414
415 if let Some(key) = tetra_key_to_egui_key(*key) {
416 self.raw_input.events.push(egui::Event::Key {
417 key,
418 pressed: true,
419 modifiers: self.raw_input.modifiers,
420 });
421 }
422 }
423 tetra::Event::KeyReleased { key } => {
424 match key {
425 tetra::input::Key::LeftCtrl | tetra::input::Key::RightCtrl => {
426 self.raw_input.modifiers.ctrl = false;
427 self.raw_input.modifiers.command = false;
428 }
429 tetra::input::Key::LeftShift | tetra::input::Key::RightShift => {
430 self.raw_input.modifiers.shift = false;
431 }
432 tetra::input::Key::LeftAlt | tetra::input::Key::RightAlt => {
433 self.raw_input.modifiers.alt = false;
434 }
435 _ => {}
436 }
437 if let Some(key) = tetra_key_to_egui_key(*key) {
438 self.raw_input.events.push(egui::Event::Key {
439 key,
440 pressed: false,
441 modifiers: self.raw_input.modifiers,
442 });
443 }
444 }
445 tetra::Event::MouseButtonPressed { button } => {
446 if let Some(button) = tetra_mouse_button_to_egui_pointer_button(*button) {
447 self.raw_input.events.push(egui::Event::PointerButton {
448 pos: tetra_vec2_to_egui_pos2(tetra::input::get_mouse_position(ctx)),
449 button,
450 pressed: true,
451 modifiers: self.raw_input.modifiers,
452 });
453 }
454 }
455 tetra::Event::MouseButtonReleased { button } => {
456 if let Some(button) = tetra_mouse_button_to_egui_pointer_button(*button) {
457 self.raw_input.events.push(egui::Event::PointerButton {
458 pos: tetra_vec2_to_egui_pos2(tetra::input::get_mouse_position(ctx)),
459 button,
460 pressed: false,
461 modifiers: self.raw_input.modifiers,
462 });
463 }
464 }
465 tetra::Event::MouseMoved { position, .. } => {
466 self.raw_input
467 .events
468 .push(egui::Event::PointerMoved(tetra_vec2_to_egui_pos2(
469 *position,
470 )));
471 }
472 tetra::Event::MouseWheelMoved { amount } => {
473 if tetra::input::is_key_down(ctx, tetra::input::Key::LeftCtrl)
474 || tetra::input::is_key_down(ctx, tetra::input::Key::RightCtrl)
475 {
476 self.raw_input
477 .events
478 .push(egui::Event::Zoom(ZOOM_SENSITIVITY.powi(amount.y)));
479 } else {
480 self.raw_input.events.push(egui::Event::Scroll(
481 egui::vec2(amount.x as f32, amount.y as f32) * SCROLL_SENSITIVITY,
482 ));
483 }
484 }
485 tetra::Event::TextInput { text } => {
486 self.raw_input.events.push(egui::Event::Text(text.clone()));
487 }
488 _ => {}
489 }
490 Ok(())
491 }
492
493 pub fn begin_frame(&mut self, ctx: &mut tetra::Context) -> Result<(), Error> {
495 let now = Instant::now();
496 self.raw_input.screen_rect = Some(egui::Rect {
497 min: egui::pos2(0.0, 0.0),
498 max: egui::pos2(
499 tetra::window::get_width(ctx) as f32,
500 tetra::window::get_height(ctx) as f32,
501 ),
502 });
503 self.raw_input.predicted_dt = (now - self.last_frame_time).as_secs_f32();
504 self.last_frame_time = now;
505 self.meshes.clear();
506 self.ctx.begin_frame(self.raw_input.take());
507 if self.texture.is_none() {
508 self.texture = Some(egui_font_image_to_tetra_texture(
509 ctx,
510 self.ctx.font_image(),
511 )?);
512 }
513 Ok(())
514 }
515
516 pub fn end_frame(&mut self, ctx: &mut tetra::Context) -> Result<(), Error> {
518 let (output, shapes) = self.ctx.end_frame();
519 if let Some(texture) = &self.texture {
520 let clipped_meshes = self.ctx.tessellate(shapes);
521 for ClippedMesh(rect, mesh) in clipped_meshes {
522 let rect = egui_rect_to_tetra_rectangle(rect);
523 let mesh = egui_mesh_to_tetra_mesh(ctx, mesh, texture.clone())?;
524 self.meshes.push((rect, mesh));
525 }
526 }
527
528 if let Some(open_url) = &output.open_url {
530 open::that(&open_url.url)?;
531 }
532
533 if !output.copied_text.is_empty() {
535 ClipboardContext::new()?.set_contents(output.copied_text)?;
536 }
537
538 Ok(())
539 }
540
541 pub fn draw_frame(&mut self, ctx: &mut tetra::Context) {
546 graphics::set_blend_mode(ctx, BlendMode::Alpha(BlendAlphaMode::Premultiplied));
547 for (rect, mesh) in &self.meshes {
548 graphics::set_scissor(ctx, *rect);
549 mesh.draw(ctx, tetra::math::Vec2::zero());
550 }
551 graphics::reset_scissor(ctx);
552 graphics::reset_blend_mode(ctx);
553 }
554}
555
556impl Default for EguiWrapper {
557 fn default() -> Self {
558 Self::new()
559 }
560}
561
562#[allow(unused_variables)]
570pub trait State<E: From<Error> = Error> {
571 fn ui(&mut self, ctx: &mut tetra::Context, egui_ctx: &egui::CtxRef) -> Result<(), E> {
573 Ok(())
574 }
575
576 fn update(&mut self, ctx: &mut tetra::Context, egui_ctx: &egui::CtxRef) -> Result<(), E> {
578 Ok(())
579 }
580
581 fn draw(&mut self, ctx: &mut tetra::Context, egui_ctx: &egui::CtxRef) -> Result<(), E> {
583 Ok(())
584 }
585
586 fn event(
591 &mut self,
592 ctx: &mut tetra::Context,
593 egui_ctx: &egui::CtxRef,
594 event: Event,
595 ) -> Result<(), E> {
596 Ok(())
597 }
598}
599
600pub struct StateWrapper<E: From<Error>> {
603 events: Vec<tetra::Event>,
604 state: Box<dyn State<E>>,
605 egui: EguiWrapper,
606}
607
608impl<E: From<Error>> StateWrapper<E> {
609 pub fn new(state: impl State<E> + 'static) -> Self {
611 Self {
612 events: vec![],
613 state: Box::new(state),
614 egui: EguiWrapper::new(),
615 }
616 }
617
618 pub fn ctx(&self) -> &egui::CtxRef {
620 self.egui.ctx()
621 }
622}
623
624impl<E: From<Error>> tetra::State<E> for StateWrapper<E> {
649 fn update(&mut self, ctx: &mut tetra::Context) -> Result<(), E> {
650 self.egui.begin_frame(ctx)?;
651 self.state.ui(ctx, self.egui.ctx())?;
652 self.egui.end_frame(ctx)?;
653
654 for event in self.events.drain(..) {
655 match &event {
656 Event::KeyPressed { .. } | Event::KeyReleased { .. } => {
657 if self.egui.ctx().wants_keyboard_input() {
658 continue;
659 }
660 }
661 Event::MouseButtonPressed { .. } | Event::MouseButtonReleased { .. } => {
662 if self.egui.ctx().is_using_pointer() {
663 continue;
664 }
665 }
666 Event::MouseMoved { .. } => {
667 if self.egui.ctx().is_using_pointer() {
668 continue;
669 }
670 }
671 Event::MouseWheelMoved { .. } => {
672 if self.egui.ctx().is_using_pointer() {
673 continue;
674 }
675 }
676 _ => {}
677 }
678 self.state.event(ctx, self.egui.ctx(), event)?;
679 }
680
681 self.state.update(ctx, self.egui.ctx())
682 }
683
684 fn draw(&mut self, ctx: &mut tetra::Context) -> Result<(), E> {
685 self.state.draw(ctx, self.egui.ctx())?;
686 self.egui.draw_frame(ctx);
687 Ok(())
688 }
689
690 fn event(&mut self, ctx: &mut tetra::Context, event: Event) -> Result<(), E> {
691 self.egui.event(ctx, &event)?;
692 self.events.push(event);
693 Ok(())
694 }
695}