mod input;
mod painter;
#[cfg(target_arch = "wasm32")]
fn getrandom(buf: &mut [u8]) -> Result<(), getrandom::Error> {
for value in buf {
*value = quad_rand::rand() as u8;
}
Ok(())
}
#[cfg(target_arch = "wasm32")]
getrandom::register_custom_getrandom!(getrandom);
use egui::CursorIcon;
use miniquad as mq;
pub use painter::CallbackFn;
#[cfg(target_os = "macos")] use copypasta::ClipboardProvider;
pub struct EguiMq {
native_dpi_scale: f32,
egui_ctx: egui::Context,
egui_input: egui::RawInput,
painter: painter::Painter,
#[cfg(target_os = "macos")]
clipboard: Option<copypasta::ClipboardContext>,
shapes: Option<Vec<egui::epaint::ClippedShape>>,
textures_delta: egui::TexturesDelta,
}
impl EguiMq {
pub fn new(mq_ctx: &mut mq::Context) -> Self {
let native_dpi_scale = mq_ctx.dpi_scale();
Self {
native_dpi_scale,
egui_ctx: egui::Context::default(),
painter: painter::Painter::new(mq_ctx),
egui_input: egui::RawInput {
pixels_per_point: Some(native_dpi_scale),
..Default::default()
},
#[cfg(target_os = "macos")]
clipboard: init_clipboard(),
shapes: None,
textures_delta: Default::default(),
}
}
pub fn egui_ctx(&self) -> &egui::Context {
&self.egui_ctx
}
pub fn run(
&mut self,
mq_ctx: &mut mq::Context,
run_ui: impl FnOnce(&mut mq::Context, &egui::Context),
) {
input::on_frame_start(&mut self.egui_input, &self.egui_ctx, mq_ctx);
if self.native_dpi_scale != mq_ctx.dpi_scale() {
self.native_dpi_scale = mq_ctx.dpi_scale();
self.egui_input.pixels_per_point = Some(self.native_dpi_scale);
}
let full_output = self
.egui_ctx
.run(self.egui_input.take(), |egui_ctx| run_ui(mq_ctx, egui_ctx));
let egui::FullOutput {
platform_output,
repaint_after: _, textures_delta,
shapes,
} = full_output;
if self.shapes.is_some() {
eprintln!("Egui contents not drawn. You need to call `draw` after calling `run`");
}
self.shapes = Some(shapes);
self.textures_delta.append(textures_delta);
let egui::PlatformOutput {
cursor_icon,
open_url,
copied_text,
events: _, text_cursor_pos: _, mutable_text_under_cursor: _, } = platform_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 meshes = self.egui_ctx.tessellate(shapes);
self.painter.paint_and_update_textures(
mq_ctx,
meshes,
&self.textures_delta,
&self.egui_ctx,
);
self.textures_delta.clear();
} else {
eprintln!("Failed to draw egui. You need to call `end_frame` before calling `draw`");
}
}
pub fn mouse_motion_event(&mut self, x: f32, y: f32) {
let pos = egui::pos2(
x as f32 / self.egui_ctx.pixels_per_point(),
y as f32 / self.egui_ctx.pixels_per_point(),
);
self.egui_input.events.push(egui::Event::PointerMoved(pos))
}
pub fn mouse_wheel_event(&mut self, dx: f32, dy: f32) {
let delta = egui::vec2(dx, dy)
* if cfg!(target_arch = "wasm32") {
1.0
} else {
8.0
};
let event = if self.egui_input.modifiers.ctrl {
egui::Event::Zoom((delta.y / 200.0).exp())
} else {
egui::Event::Scroll(delta)
};
self.egui_input.events.push(event);
}
pub fn mouse_button_down_event(
&mut self,
_: &mut mq::Context,
mb: mq::MouseButton,
x: f32,
y: f32,
) {
let pos = egui::pos2(
x as f32 / self.egui_ctx.pixels_per_point(),
y as f32 / self.egui_ctx.pixels_per_point(),
);
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,
_: &mut mq::Context,
mb: mq::MouseButton,
x: f32,
y: f32,
) {
let pos = egui::pos2(
x as f32 / self.egui_ctx.pixels_per_point(),
y as f32 / self.egui_ctx.pixels_per_point(),
);
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_ctx.clipboard_set(&text);
}
#[cfg(not(target_os = "macos"))]
fn get_clipboard(&mut self, mq_ctx: &mut mq::Context) -> Option<String> {
mq_ctx.clipboard_get()
}
#[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::ResizeColumn
| egui::CursorIcon::ResizeEast
| egui::CursorIcon::ResizeNorth
| egui::CursorIcon::ResizeNorthEast
| egui::CursorIcon::ResizeNorthWest
| egui::CursorIcon::ResizeRow
| egui::CursorIcon::ResizeSouth
| egui::CursorIcon::ResizeSouthEast
| egui::CursorIcon::ResizeSouthWest
| egui::CursorIcon::ResizeWest
| egui::CursorIcon::VerticalText
| egui::CursorIcon::ZoomIn
| egui::CursorIcon::ZoomOut => None,
}
}