1mod input;
100mod painter;
101
102#[cfg(target_arch = "wasm32")]
106fn getrandom(buf: &mut [u8]) -> Result<(), getrandom::Error> {
107 for value in buf {
109 *value = quad_rand::rand() as u8;
110 }
111 Ok(())
112}
113#[cfg(target_arch = "wasm32")]
114getrandom::register_custom_getrandom!(getrandom);
115
116use egui::CursorIcon;
119use miniquad as mq;
120
121pub use painter::CallbackFn;
122
123#[cfg(target_os = "macos")] use copypasta::ClipboardProvider;
125
126pub struct EguiMq {
130 native_dpi_scale: f32,
132 pixels_per_point: f32,
134 egui_ctx: egui::Context,
135 egui_input: egui::RawInput,
136 painter: painter::Painter,
137 #[cfg(target_os = "macos")]
138 clipboard: Option<copypasta::ClipboardContext>,
139 shapes: Option<Vec<egui::epaint::ClippedShape>>,
140 textures_delta: egui::TexturesDelta,
141}
142
143impl EguiMq {
144 pub fn new(mq_ctx: &mut dyn mq::RenderingBackend) -> Self {
145 let native_dpi_scale = miniquad::window::dpi_scale();
146
147 Self {
148 native_dpi_scale,
149 pixels_per_point: native_dpi_scale,
150 egui_ctx: egui::Context::default(),
151 painter: painter::Painter::new(mq_ctx),
152 egui_input: egui::RawInput::default(),
153 #[cfg(target_os = "macos")]
154 clipboard: init_clipboard(),
155 shapes: None,
156 textures_delta: Default::default(),
157 }
158 }
159
160 pub fn egui_ctx(&self) -> &egui::Context {
164 &self.egui_ctx
165 }
166
167 pub fn run(
169 &mut self,
170 mq_ctx: &mut dyn mq::RenderingBackend,
171 mut run_ui: impl FnMut(&mut dyn mq::RenderingBackend, &egui::Context),
172 ) {
173 input::on_frame_start(&mut self.egui_input, &self.egui_ctx);
174
175 if self.native_dpi_scale != miniquad::window::dpi_scale() {
176 self.native_dpi_scale = miniquad::window::dpi_scale();
178 self.egui_input
179 .viewports
180 .get_mut(&self.egui_input.viewport_id)
181 .unwrap()
182 .native_pixels_per_point = Some(self.native_dpi_scale);
183 }
184
185 let full_output = self
186 .egui_ctx
187 .run(self.egui_input.take(), |egui_ctx| run_ui(mq_ctx, egui_ctx));
188
189 let egui::FullOutput {
190 platform_output,
191 textures_delta,
192 shapes,
193 pixels_per_point,
194 viewport_output: _viewport_output, } = full_output;
196
197 if self.shapes.is_some() {
198 eprintln!("Egui contents not drawn. You need to call `draw` after calling `run`");
199 }
200 self.shapes = Some(shapes);
201 self.pixels_per_point = pixels_per_point;
202 self.textures_delta.append(textures_delta);
203
204 let egui::PlatformOutput {
205 commands,
206 cursor_icon,
207 events: _, ime: _, mutable_text_under_cursor: _, ..
211 } = platform_output;
212
213 for command in commands {
214 match command {
215 egui::OutputCommand::OpenUrl(open_url) => {
216 quad_url::link_open(&open_url.url, open_url.new_tab);
217 }
218 egui::OutputCommand::CopyText(copied_text) => {
219 self.set_clipboard(copied_text);
220 }
221 egui::OutputCommand::CopyImage(_) => (), }
223 }
224
225 if cursor_icon == egui::CursorIcon::None {
226 miniquad::window::show_mouse(false);
227 } else {
228 miniquad::window::show_mouse(true);
229 let mq_cursor_icon = to_mq_cursor_icon(cursor_icon);
230 let mq_cursor_icon = mq_cursor_icon.unwrap_or(mq::CursorIcon::Default);
231 miniquad::window::set_mouse_cursor(mq_cursor_icon);
232 }
233 }
234
235 pub fn draw(&mut self, mq_ctx: &mut dyn mq::RenderingBackend) {
238 if let Some(shapes) = self.shapes.take() {
239 let meshes = self.egui_ctx.tessellate(shapes, self.pixels_per_point);
240 self.painter.paint_and_update_textures(
241 mq_ctx,
242 meshes,
243 &self.textures_delta,
244 &self.egui_ctx,
245 );
246 self.textures_delta.clear();
247 } else {
248 eprintln!("Failed to draw egui. You need to call `end_frame` before calling `draw`");
249 }
250 }
251
252 pub fn mouse_motion_event(&mut self, x: f32, y: f32) {
254 let pos = egui::pos2(
255 x / self.egui_ctx.pixels_per_point(),
256 y / self.egui_ctx.pixels_per_point(),
257 );
258 self.egui_input.events.push(egui::Event::PointerMoved(pos))
259 }
260
261 pub fn mouse_wheel_event(&mut self, dx: f32, dy: f32) {
263 let delta = egui::vec2(dx, dy);
264 let modifiers = self.egui_input.modifiers;
265
266 self.egui_input.events.push(egui::Event::MouseWheel {
267 modifiers,
268 unit: egui::MouseWheelUnit::Line,
269 delta,
270 });
271 }
272
273 pub fn mouse_button_down_event(&mut self, mb: mq::MouseButton, x: f32, y: f32) {
275 let pos = egui::pos2(
276 x / self.egui_ctx.pixels_per_point(),
277 y / self.egui_ctx.pixels_per_point(),
278 );
279 let button = to_egui_button(mb);
280 self.egui_input.events.push(egui::Event::PointerButton {
281 pos,
282 button,
283 pressed: true,
284 modifiers: self.egui_input.modifiers,
285 })
286 }
287
288 pub fn mouse_button_up_event(&mut self, mb: mq::MouseButton, x: f32, y: f32) {
290 let pos = egui::pos2(
291 x / self.egui_ctx.pixels_per_point(),
292 y / self.egui_ctx.pixels_per_point(),
293 );
294 let button = to_egui_button(mb);
295
296 self.egui_input.events.push(egui::Event::PointerButton {
297 pos,
298 button,
299 pressed: false,
300 modifiers: self.egui_input.modifiers,
301 })
302 }
303
304 pub fn char_event(&mut self, chr: char) {
306 if input::is_printable_char(chr)
307 && !self.egui_input.modifiers.ctrl
308 && !self.egui_input.modifiers.mac_cmd
309 {
310 self.egui_input
311 .events
312 .push(egui::Event::Text(chr.to_string()));
313 }
314 }
315
316 pub fn key_down_event(&mut self, keycode: mq::KeyCode, keymods: mq::KeyMods) {
318 let modifiers = input::egui_modifiers_from_mq_modifiers(keymods);
319 self.egui_input.modifiers = modifiers;
320
321 if modifiers.command && keycode == mq::KeyCode::X {
322 self.egui_input.events.push(egui::Event::Cut);
323 } else if modifiers.command && keycode == mq::KeyCode::C {
324 self.egui_input.events.push(egui::Event::Copy);
325 } else if modifiers.command && keycode == mq::KeyCode::V {
326 if let Some(text) = self.get_clipboard() {
327 self.egui_input.events.push(egui::Event::Text(text));
328 }
329 } else if let Some(key) = input::egui_key_from_mq_key(keycode) {
330 self.egui_input.events.push(egui::Event::Key {
331 key,
332 pressed: true,
333 modifiers,
334 repeat: false, physical_key: None, })
337 }
338 }
339
340 pub fn key_up_event(&mut self, keycode: mq::KeyCode, keymods: mq::KeyMods) {
342 let modifiers = input::egui_modifiers_from_mq_modifiers(keymods);
343 self.egui_input.modifiers = modifiers;
344 if let Some(key) = input::egui_key_from_mq_key(keycode) {
345 self.egui_input.events.push(egui::Event::Key {
346 key,
347 pressed: false,
348 modifiers,
349 repeat: false, physical_key: None, })
352 }
353 }
354
355 #[cfg(not(target_os = "macos"))]
356 fn set_clipboard(&mut self, text: String) {
357 mq::window::clipboard_set(&text);
358 }
359
360 #[cfg(not(target_os = "macos"))]
361 fn get_clipboard(&mut self) -> Option<String> {
362 mq::window::clipboard_get()
363 }
364
365 #[cfg(target_os = "macos")]
366 fn set_clipboard(&mut self, text: String) {
367 if let Some(clipboard) = &mut self.clipboard {
368 if let Err(err) = clipboard.set_contents(text) {
369 eprintln!("Copy/Cut error: {}", err);
370 }
371 }
372 }
373
374 #[cfg(target_os = "macos")]
375 fn get_clipboard(&mut self) -> Option<String> {
376 if let Some(clipboard) = &mut self.clipboard {
377 match clipboard.get_contents() {
378 Ok(contents) => Some(contents),
379 Err(err) => {
380 eprintln!("Paste error: {}", err);
381 None
382 }
383 }
384 } else {
385 None
386 }
387 }
388}
389
390#[cfg(target_os = "macos")]
391fn init_clipboard() -> Option<copypasta::ClipboardContext> {
392 match copypasta::ClipboardContext::new() {
393 Ok(clipboard) => Some(clipboard),
394 Err(err) => {
395 eprintln!("Failed to initialize clipboard: {}", err);
396 None
397 }
398 }
399}
400
401fn to_egui_button(mb: mq::MouseButton) -> egui::PointerButton {
402 match mb {
403 mq::MouseButton::Left => egui::PointerButton::Primary,
404 mq::MouseButton::Right => egui::PointerButton::Secondary,
405 mq::MouseButton::Middle => egui::PointerButton::Middle,
406 mq::MouseButton::Unknown => egui::PointerButton::Primary,
407 }
408}
409
410fn to_mq_cursor_icon(cursor_icon: egui::CursorIcon) -> Option<mq::CursorIcon> {
411 match cursor_icon {
412 CursorIcon::None => None,
414
415 egui::CursorIcon::Default => Some(mq::CursorIcon::Default),
416 egui::CursorIcon::PointingHand => Some(mq::CursorIcon::Pointer),
417 egui::CursorIcon::Text => Some(mq::CursorIcon::Text),
418 egui::CursorIcon::ResizeHorizontal => Some(mq::CursorIcon::EWResize),
419 egui::CursorIcon::ResizeVertical => Some(mq::CursorIcon::NSResize),
420 egui::CursorIcon::ResizeNeSw => Some(mq::CursorIcon::NESWResize),
421 egui::CursorIcon::ResizeNwSe => Some(mq::CursorIcon::NWSEResize),
422 egui::CursorIcon::Help => Some(mq::CursorIcon::Help),
423 egui::CursorIcon::Wait => Some(mq::CursorIcon::Wait),
424 egui::CursorIcon::Crosshair => Some(mq::CursorIcon::Crosshair),
425 egui::CursorIcon::Move => Some(mq::CursorIcon::Move),
426 egui::CursorIcon::NotAllowed => Some(mq::CursorIcon::NotAllowed),
427
428 egui::CursorIcon::AllScroll => Some(mq::CursorIcon::Move),
430 egui::CursorIcon::Progress => Some(mq::CursorIcon::Wait),
431
432 egui::CursorIcon::Grab | egui::CursorIcon::Grabbing => None,
434
435 egui::CursorIcon::Alias
437 | egui::CursorIcon::Cell
438 | egui::CursorIcon::ContextMenu
439 | egui::CursorIcon::Copy
440 | egui::CursorIcon::NoDrop
441 | egui::CursorIcon::ResizeColumn
442 | egui::CursorIcon::ResizeEast
443 | egui::CursorIcon::ResizeNorth
444 | egui::CursorIcon::ResizeNorthEast
445 | egui::CursorIcon::ResizeNorthWest
446 | egui::CursorIcon::ResizeRow
447 | egui::CursorIcon::ResizeSouth
448 | egui::CursorIcon::ResizeSouthEast
449 | egui::CursorIcon::ResizeSouthWest
450 | egui::CursorIcon::ResizeWest
451 | egui::CursorIcon::VerticalText
452 | egui::CursorIcon::ZoomIn
453 | egui::CursorIcon::ZoomOut => None,
454 }
455}