mod input;
mod painter;
use egui::CursorIcon;
use miniquad as mq;
#[cfg(target_os = "macos")]
use copypasta::ClipboardProvider;
pub struct EguiMq {
egui_ctx: egui::CtxRef,
egui_input: egui::RawInput,
painter: painter::Painter,
#[cfg(target_os = "macos")]
clipboard: Option<copypasta::ClipboardContext>,
shapes: Option<Vec<egui::epaint::ClippedShape>>,
}
impl EguiMq {
pub fn new(mq_ctx: &mut mq::Context) -> Self {
Self {
egui_ctx: egui::CtxRef::default(),
painter: painter::Painter::new(mq_ctx),
egui_input: Default::default(),
#[cfg(target_os = "macos")]
clipboard: init_clipboard(),
shapes: None,
}
}
pub fn egui_ctx(&self) -> &egui::CtxRef {
&self.egui_ctx
}
pub fn begin_frame(&mut self, mq_ctx: &mut mq::Context) {
input::on_frame_start(&mut self.egui_input, mq_ctx);
self.egui_ctx.begin_frame(self.egui_input.take());
}
pub fn end_frame(&mut self, mq_ctx: &mut mq::Context) {
let (output, shapes) = self.egui_ctx.end_frame();
if self.shapes.is_some() {
eprintln!(
"Egui contents not drawed. You need to call `draw` after calling `end_frame`"
);
}
self.shapes = Some(shapes);
let egui::Output {
cursor_icon,
open_url,
copied_text,
needs_repaint: _,
events: _,
text_cursor: _,
} = output;
if let Some(url) = open_url {
quad_url::link_open(&url.url, url.new_tab);
}
if cursor_icon == egui::CursorIcon::None {
mq_ctx.show_mouse(false);
} else {
mq_ctx.show_mouse(true);
let mq_cursor_icon = to_mq_cursor_icon(cursor_icon);
let mq_cursor_icon = mq_cursor_icon.unwrap_or(mq::CursorIcon::Default);
mq_ctx.set_mouse_cursor(mq_cursor_icon);
}
if !copied_text.is_empty() {
self.set_clipboard(mq_ctx, copied_text);
}
}
pub fn draw(&mut self, mq_ctx: &mut mq::Context) {
if let Some(shapes) = self.shapes.take() {
let paint_jobs = self.egui_ctx.tessellate(shapes);
self.painter
.paint(mq_ctx, paint_jobs, &self.egui_ctx.texture());
} else {
eprintln!("Failed to draw egui. You need to call `end_frame` before calling `draw`");
}
}
pub fn mouse_motion_event(&mut self, ctx: &mut mq::Context, x: f32, y: f32) {
let pos = egui::pos2(x as f32 / ctx.dpi_scale(), y as f32 / ctx.dpi_scale());
self.egui_input.events.push(egui::Event::PointerMoved(pos))
}
pub fn mouse_wheel_event(&mut self, ctx: &mut mq::Context, dx: f32, dy: f32) {
self.egui_input.scroll_delta += egui::vec2(dx, dy) * ctx.dpi_scale();
}
pub fn mouse_button_down_event(
&mut self,
ctx: &mut mq::Context,
mb: mq::MouseButton,
x: f32,
y: f32,
) {
let pos = egui::pos2(x as f32 / ctx.dpi_scale(), y as f32 / ctx.dpi_scale());
let button = to_egui_button(mb);
self.egui_input.events.push(egui::Event::PointerButton {
pos,
button,
pressed: true,
modifiers: self.egui_input.modifiers,
})
}
pub fn mouse_button_up_event(
&mut self,
ctx: &mut mq::Context,
mb: mq::MouseButton,
x: f32,
y: f32,
) {
let pos = egui::pos2(x as f32 / ctx.dpi_scale(), y as f32 / ctx.dpi_scale());
let button = to_egui_button(mb);
self.egui_input.events.push(egui::Event::PointerButton {
pos,
button,
pressed: false,
modifiers: self.egui_input.modifiers,
})
}
pub fn char_event(&mut self, chr: char) {
if input::is_printable_char(chr)
&& !self.egui_input.modifiers.ctrl
&& !self.egui_input.modifiers.mac_cmd
{
self.egui_input
.events
.push(egui::Event::Text(chr.to_string()));
}
}
pub fn key_down_event(
&mut self,
mq_ctx: &mut mq::Context,
keycode: mq::KeyCode,
keymods: mq::KeyMods,
) {
let modifiers = input::egui_modifiers_from_mq_modifiers(keymods);
self.egui_input.modifiers = modifiers;
if modifiers.command && keycode == mq::KeyCode::X {
self.egui_input.events.push(egui::Event::Cut);
} else if modifiers.command && keycode == mq::KeyCode::C {
self.egui_input.events.push(egui::Event::Copy);
} else if modifiers.command && keycode == mq::KeyCode::V {
if let Some(text) = self.get_clipboard(mq_ctx) {
self.egui_input.events.push(egui::Event::Text(text));
}
} else if let Some(key) = input::egui_key_from_mq_key(keycode) {
self.egui_input.events.push(egui::Event::Key {
key,
pressed: true,
modifiers,
})
}
}
pub fn key_up_event(&mut self, keycode: mq::KeyCode, keymods: mq::KeyMods) {
let modifiers = input::egui_modifiers_from_mq_modifiers(keymods);
self.egui_input.modifiers = modifiers;
if let Some(key) = input::egui_key_from_mq_key(keycode) {
self.egui_input.events.push(egui::Event::Key {
key,
pressed: false,
modifiers,
})
}
}
#[cfg(not(target_os = "macos"))]
fn set_clipboard(&mut self, mq_ctx: &mut mq::Context, text: String) {
mq::clipboard::set(mq_ctx, text.as_str());
}
#[cfg(not(target_os = "macos"))]
fn get_clipboard(&mut self, mq_ctx: &mut mq::Context) -> Option<String> {
mq::clipboard::get(mq_ctx)
}
#[cfg(target_os = "macos")]
fn set_clipboard(&mut self, _mq_ctx: &mut mq::Context, text: String) {
if let Some(clipboard) = &mut self.clipboard {
if let Err(err) = clipboard.set_contents(text) {
eprintln!("Copy/Cut error: {}", err);
}
}
}
#[cfg(target_os = "macos")]
fn get_clipboard(&mut self, _mq_ctx: &mut mq::Context) -> Option<String> {
if let Some(clipboard) = &mut self.clipboard {
match clipboard.get_contents() {
Ok(contents) => Some(contents),
Err(err) => {
eprintln!("Paste error: {}", err);
None
}
}
} else {
None
}
}
}
#[cfg(target_os = "macos")]
fn init_clipboard() -> Option<copypasta::ClipboardContext> {
match copypasta::ClipboardContext::new() {
Ok(clipboard) => Some(clipboard),
Err(err) => {
eprintln!("Failed to initialize clipboard: {}", err);
None
}
}
}
fn to_egui_button(mb: mq::MouseButton) -> egui::PointerButton {
match mb {
mq::MouseButton::Left => egui::PointerButton::Primary,
mq::MouseButton::Right => egui::PointerButton::Secondary,
mq::MouseButton::Middle => egui::PointerButton::Middle,
mq::MouseButton::Unknown => egui::PointerButton::Primary,
}
}
fn to_mq_cursor_icon(cursor_icon: egui::CursorIcon) -> Option<mq::CursorIcon> {
match cursor_icon {
CursorIcon::None => None,
egui::CursorIcon::Default => Some(mq::CursorIcon::Default),
egui::CursorIcon::PointingHand => Some(mq::CursorIcon::Pointer),
egui::CursorIcon::Text => Some(mq::CursorIcon::Text),
egui::CursorIcon::ResizeHorizontal => Some(mq::CursorIcon::EWResize),
egui::CursorIcon::ResizeVertical => Some(mq::CursorIcon::NSResize),
egui::CursorIcon::ResizeNeSw => Some(mq::CursorIcon::NESWResize),
egui::CursorIcon::ResizeNwSe => Some(mq::CursorIcon::NWSEResize),
egui::CursorIcon::Help => Some(mq::CursorIcon::Help),
egui::CursorIcon::Wait => Some(mq::CursorIcon::Wait),
egui::CursorIcon::Crosshair => Some(mq::CursorIcon::Crosshair),
egui::CursorIcon::Move => Some(mq::CursorIcon::Move),
egui::CursorIcon::NotAllowed => Some(mq::CursorIcon::NotAllowed),
egui::CursorIcon::AllScroll => Some(mq::CursorIcon::Move),
egui::CursorIcon::Progress => Some(mq::CursorIcon::Wait),
egui::CursorIcon::Grab | egui::CursorIcon::Grabbing => None,
egui::CursorIcon::Alias
| egui::CursorIcon::Cell
| egui::CursorIcon::ContextMenu
| egui::CursorIcon::Copy
| egui::CursorIcon::NoDrop
| egui::CursorIcon::VerticalText
| egui::CursorIcon::ZoomIn
| egui::CursorIcon::ZoomOut => None,
}
}