1#[deny(missing_docs)]
2use egui::{Context, Event, FullOutput, Pos2, RawInput, Rect, Vec2};
3use egui::{PlatformOutput, ViewportId, ViewportInfo};
4use egui_glow::Painter;
5#[cfg(feature = "desktop_integration")]
6use smithay::desktop::space::{RenderZindex, SpaceElement};
7use smithay::{
8 backend::{
9 allocator::Fourcc,
10 input::{ButtonState, Device, DeviceCapability, KeyState, MouseButton},
11 renderer::{
12 element::{
13 texture::{TextureRenderBuffer, TextureRenderElement},
14 Kind,
15 },
16 gles::{GlesError, GlesTexture},
17 glow::GlowRenderer,
18 Bind, Frame, Offscreen, Renderer,
19 },
20 },
21 input::{
22 keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
23 pointer::{
24 AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
25 GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
26 GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
27 PointerTarget, RelativeMotionEvent,
28 },
29 Seat, SeatHandler,
30 },
31 utils::{IsAlive, Logical, Physical, Point, Rectangle, Serial, Size, Transform},
32};
33use xkbcommon::xkb::Keycode;
34
35use std::{
36 cell::RefCell,
37 collections::HashMap,
38 fmt,
39 rc::Rc,
40 sync::{Arc, Mutex},
41 time::Instant,
42};
43
44mod input;
45pub use self::input::{convert_button, convert_key, convert_modifiers};
46
47#[derive(Debug, Clone)]
49pub struct EguiState {
50 inner: Arc<Mutex<EguiInner>>,
51 ctx: Context,
52 start_time: Instant,
53}
54
55impl PartialEq for EguiState {
56 fn eq(&self, other: &Self) -> bool {
57 self.ctx == other.ctx
58 }
59}
60
61struct EguiInner {
62 pointers: usize,
63 last_pointer_position: Point<i32, Logical>,
64 area: Rectangle<i32, Logical>,
65 last_modifiers: ModifiersState,
66 last_output: Option<PlatformOutput>,
67 pressed: Vec<(Option<egui::Key>, Keycode)>,
68 focused: bool,
69 events: Vec<Event>,
70 kbd: Option<input::KbdInternal>,
71 #[cfg(feature = "desktop_integration")]
72 z_index: u8,
73}
74
75impl fmt::Debug for EguiInner {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 let mut d = f.debug_struct("EguiInner");
78 d.field("pointers", &self.pointers)
79 .field("last_pointer_position", &self.last_pointer_position)
80 .field("area", &self.area)
81 .field("last_modifiers", &self.last_modifiers)
82 .field("last_output", &self.last_output.as_ref().map(|_| "..."))
83 .field("pressed", &self.pressed)
84 .field("focused", &self.focused)
85 .field("events", &self.events)
86 .field("kbd", &self.kbd);
87
88 #[cfg(feature = "desktop_integration")]
89 {
90 d.field("z_index", &self.z_index);
91 }
92
93 d.finish()
94 }
95}
96
97struct GlState {
98 painter: Painter,
99 render_buffers: HashMap<usize, TextureRenderBuffer<GlesTexture>>,
100}
101type UserDataType = Rc<RefCell<GlState>>;
102
103impl EguiState {
104 pub fn new(area: Rectangle<i32, Logical>) -> EguiState {
106 let ctx = Context::default();
107 #[cfg(feature = "image")]
108 egui_extras::install_image_loaders(&ctx);
109 EguiState {
110 ctx,
111 start_time: Instant::now(),
112 inner: Arc::new(Mutex::new(EguiInner {
113 pointers: 0,
114 last_pointer_position: (0, 0).into(),
115 area,
116 last_modifiers: ModifiersState::default(),
117 last_output: None,
118 events: Vec::new(),
119 focused: false,
120 pressed: Vec::new(),
121 kbd: match input::KbdInternal::new() {
122 Some(kbd) => Some(kbd),
123 None => {
124 log::error!("Failed to initialize keymap for text input in egui.");
125 None
126 }
127 },
128 #[cfg(feature = "desktop_integration")]
129 z_index: RenderZindex::Overlay as u8,
130 })),
131 }
132 }
133
134 fn id(&self) -> usize {
135 Arc::as_ptr(&self.inner) as usize
136 }
137
138 pub fn context(&self) -> &Context {
140 &self.ctx
141 }
142
143 pub fn wants_keyboard(&self) -> bool {
145 self.ctx.wants_keyboard_input()
146 }
147
148 pub fn wants_pointer(&self) -> bool {
153 self.ctx.wants_pointer_input()
154 }
155
156 pub fn handle_device_added(&self, device: &impl Device) {
158 if device.has_capability(DeviceCapability::Pointer) {
159 self.inner.lock().unwrap().pointers += 1;
160 }
161 }
162
163 pub fn handle_device_removed(&self, device: &impl Device) {
165 let mut inner = self.inner.lock().unwrap();
166 if device.has_capability(DeviceCapability::Pointer) {
167 inner.pointers -= 1;
168 }
169 if inner.pointers == 0 {
170 inner.events.push(Event::PointerGone);
171 }
172 }
173
174 pub fn handle_keyboard(&self, handle: &KeysymHandle, pressed: bool, modifiers: ModifiersState) {
182 let mut inner = self.inner.lock().unwrap();
183 inner.last_modifiers = modifiers;
184 let key = if let Some(key) = convert_key(handle.raw_syms().iter().copied()) {
185 inner.events.push(Event::Key {
186 key,
187 physical_key: None,
188 pressed,
189 repeat: false,
190 modifiers: convert_modifiers(modifiers),
191 });
192 Some(key)
193 } else {
194 None
195 };
196
197 if pressed {
198 inner.pressed.push((key, handle.raw_code()));
199 } else {
200 inner.pressed.retain(|(_, code)| code != &handle.raw_code());
201 }
202
203 if let Some(kbd) = inner.kbd.as_mut() {
204 kbd.key_input(handle.raw_code().raw(), pressed);
205
206 if pressed {
207 let utf8 = kbd.get_utf8(handle.raw_code().raw());
208 inner.events.push(Event::Text(utf8));
212 }
213 }
214 }
215
216 pub fn handle_pointer_motion(&self, position: Point<i32, Logical>) {
218 let mut inner = self.inner.lock().unwrap();
219 inner.last_pointer_position = position;
220 inner.events.push(Event::PointerMoved(Pos2::new(
221 position.x as f32,
222 position.y as f32,
223 )))
224 }
225
226 pub fn handle_pointer_button(&self, button: MouseButton, pressed: bool) {
232 if let Some(button) = convert_button(button) {
233 let mut inner = self.inner.lock().unwrap();
234 let last_pos = inner.last_pointer_position;
235 let modifiers = convert_modifiers(inner.last_modifiers);
236 inner.events.push(Event::PointerButton {
237 pos: Pos2::new(last_pos.x as f32, last_pos.y as f32),
238 button,
239 pressed,
240 modifiers,
241 })
242 }
243 }
244
245 pub fn handle_pointer_axis(&self, x_amount: f64, y_amount: f64) {
251 let mut inner = self.inner.lock().unwrap();
252 let modifiers = convert_modifiers(inner.last_modifiers);
253 inner.events.push(Event::MouseWheel {
254 unit: egui::MouseWheelUnit::Point,
255 delta: Vec2 {
256 x: x_amount as f32,
257 y: y_amount as f32,
258 },
259 modifiers,
260 })
261 }
262
263 pub fn set_focused(&self, focused: bool) {
265 self.inner.lock().unwrap().focused = focused;
266 }
267
268 pub fn render(
280 &self,
281 ui: impl FnMut(&Context),
282 renderer: &mut GlowRenderer,
283 area: Rectangle<i32, Logical>,
284 scale: f64,
285 alpha: f32,
286 ) -> Result<TextureRenderElement<GlesTexture>, GlesError> {
287 let int_scale = scale.ceil() as i32;
288 let user_data = renderer.egl_context().user_data();
289 if user_data.get::<UserDataType>().is_none() {
290 let painter = {
291 renderer
292 .with_context(|context| Painter::new(context.clone(), "", None, false))?
293 .map_err(|_| GlesError::ShaderCompileError)?
294 };
295 renderer.egl_context().user_data().insert_if_missing(|| {
296 UserDataType::new(RefCell::new(GlState {
297 painter,
298 render_buffers: HashMap::new(),
299 }))
300 });
301 }
302
303 let mut inner = self.inner.lock().unwrap();
304 let gl_state = renderer
305 .egl_context()
306 .user_data()
307 .get::<UserDataType>()
308 .unwrap()
309 .clone();
310 let mut borrow = gl_state.borrow_mut();
311 let &mut GlState {
312 ref mut painter,
313 ref mut render_buffers,
314 ..
315 } = &mut *borrow;
316
317 let render_buffer = render_buffers.entry(self.id()).or_insert_with(|| {
318 let render_texture = renderer
319 .create_buffer(
320 Fourcc::Abgr8888,
321 area.size
322 .to_buffer(int_scale, smithay::utils::Transform::Normal),
323 )
324 .expect("Failed to create buffer");
325 TextureRenderBuffer::from_texture(
326 renderer,
327 render_texture,
328 int_scale,
329 Transform::Flipped180,
330 None,
331 )
332 });
333
334 let screen_size: Size<i32, Physical> = area.size.to_physical(int_scale);
335 let input = RawInput {
336 viewport_id: ViewportId::ROOT,
337 viewports: std::iter::once((
338 ViewportId::ROOT,
339 ViewportInfo {
340 native_pixels_per_point: Some(int_scale as f32),
341 ..Default::default()
342 },
343 ))
344 .collect(),
345 screen_rect: Some(Rect {
346 min: Pos2 { x: 0.0, y: 0.0 },
347 max: Pos2 {
348 x: screen_size.w as f32,
349 y: screen_size.h as f32,
350 },
351 }),
352 time: Some(self.start_time.elapsed().as_secs_f64()),
353 modifiers: convert_modifiers(inner.last_modifiers),
354 events: inner.events.drain(..).collect(),
355 focused: inner.focused,
356 max_texture_side: Some(painter.max_texture_side()), ..Default::default()
358 };
359
360 let FullOutput {
361 platform_output,
362 shapes,
363 textures_delta,
364 ..
365 } = self.ctx.run(input.clone(), ui);
366 inner.last_output = Some(platform_output);
367
368 let needs_recreate = inner.area != area;
369 inner.area = area;
370
371 if needs_recreate {
372 *render_buffer = {
373 let render_texture = renderer.create_buffer(
374 Fourcc::Abgr8888,
375 area.size
376 .to_buffer(int_scale, smithay::utils::Transform::Normal),
377 )?;
378 TextureRenderBuffer::from_texture(
379 renderer,
380 render_texture,
381 int_scale,
382 Transform::Flipped180,
383 None,
384 )
385 };
386 }
387
388 render_buffer.render().draw(|tex| {
389 let mut fb = renderer.bind(tex)?;
390 let physical_area = area.to_physical(int_scale);
391 {
392 let mut frame = renderer.render(&mut fb, physical_area.size, Transform::Normal)?;
393 frame.clear([0.0, 0.0, 0.0, 0.0].into(), &[physical_area])?;
394 painter.paint_and_update_textures(
395 [physical_area.size.w as u32, physical_area.size.h as u32],
396 int_scale as f32,
397 &self.ctx.tessellate(shapes, int_scale as f32),
398 &textures_delta,
399 );
400 }
401
402 let used = self.ctx.used_rect();
403 let margin = self.ctx.style().visuals.clip_rect_margin.ceil() as i32;
404 let window_shadow = self
405 .ctx
406 .style()
407 .visuals
408 .window_shadow
409 .margin()
410 .sum()
411 .max_elem()
412 .ceil() as i32;
413 let popup_shadow = self
414 .ctx
415 .style()
416 .visuals
417 .popup_shadow
418 .margin()
419 .sum()
420 .max_elem()
421 .ceil() as i32;
422 let offset = margin + Ord::max(window_shadow, popup_shadow);
423 Result::<_, GlesError>::Ok(vec![Rectangle::<i32, Logical>::from_extremities(
424 (
425 (used.min.x.floor() as i32).saturating_sub(offset),
426 (used.min.y.floor() as i32).saturating_sub(offset),
427 ),
428 (
429 (used.max.x.ceil() as i32) + (offset * 2),
430 (used.max.y.ceil() as i32) + (offset * 2),
431 ),
432 )
433 .to_buffer(int_scale, Transform::Flipped180, &area.size)])
434 })?;
435
436 Ok(TextureRenderElement::from_texture_render_buffer(
437 area.loc.to_f64().to_physical(scale),
438 &render_buffer,
439 Some(alpha),
440 None,
441 None,
442 Kind::Unspecified,
443 ))
444 }
445
446 #[cfg(feature = "desktop_integration")]
450 pub fn set_zindex(&self, idx: u8) {
451 self.inner.lock().unwrap().z_index = idx;
452 }
453
454 pub fn last_output(&self) -> Option<PlatformOutput> {
456 self.inner.lock().unwrap().last_output.take()
457 }
458}
459
460impl IsAlive for EguiState {
461 fn alive(&self) -> bool {
462 true
463 }
464}
465
466impl<D: SeatHandler> PointerTarget<D> for EguiState {
467 fn enter(&self, _seat: &Seat<D>, _data: &mut D, event: &MotionEvent) {
468 self.handle_pointer_motion(event.location.to_i32_floor())
469 }
470
471 fn motion(&self, _seat: &Seat<D>, _data: &mut D, event: &MotionEvent) {
472 self.handle_pointer_motion(event.location.to_i32_round())
473 }
474
475 fn relative_motion(&self, _seat: &Seat<D>, _data: &mut D, _event: &RelativeMotionEvent) {}
476
477 fn button(&self, _seat: &Seat<D>, _data: &mut D, event: &ButtonEvent) {
478 if let Some(button) = match event.button {
479 0x110 => Some(MouseButton::Left),
480 0x111 => Some(MouseButton::Right),
481 0x112 => Some(MouseButton::Middle),
482 0x115 => Some(MouseButton::Forward),
483 0x116 => Some(MouseButton::Back),
484 _ => None,
485 } {
486 self.handle_pointer_button(button, event.state == ButtonState::Pressed)
487 }
488 }
489
490 fn axis(&self, _seat: &Seat<D>, _data: &mut D, _frame: AxisFrame) {
491 }
494
495 fn leave(&self, _seat: &Seat<D>, _data: &mut D, _serial: Serial, _time: u32) {}
496
497 fn frame(&self, _seat: &Seat<D>, _data: &mut D) {}
498
499 fn gesture_swipe_begin(&self, _seat: &Seat<D>, _data: &mut D, _event: &GestureSwipeBeginEvent) {
500 }
501
502 fn gesture_swipe_update(
503 &self,
504 _seat: &Seat<D>,
505 _data: &mut D,
506 _event: &GestureSwipeUpdateEvent,
507 ) {
508 }
509
510 fn gesture_swipe_end(&self, _seat: &Seat<D>, _data: &mut D, _event: &GestureSwipeEndEvent) {}
511
512 fn gesture_pinch_begin(&self, _seat: &Seat<D>, _data: &mut D, _event: &GesturePinchBeginEvent) {
513 }
514
515 fn gesture_pinch_update(
516 &self,
517 _seat: &Seat<D>,
518 _data: &mut D,
519 _event: &GesturePinchUpdateEvent,
520 ) {
521 }
522
523 fn gesture_pinch_end(&self, _seat: &Seat<D>, _data: &mut D, _event: &GesturePinchEndEvent) {}
524
525 fn gesture_hold_begin(&self, _seat: &Seat<D>, _data: &mut D, _event: &GestureHoldBeginEvent) {}
526
527 fn gesture_hold_end(&self, _seat: &Seat<D>, _data: &mut D, _event: &GestureHoldEndEvent) {}
528}
529
530impl<D: SeatHandler> KeyboardTarget<D> for EguiState {
531 fn enter(&self, _seat: &Seat<D>, _data: &mut D, keys: Vec<KeysymHandle<'_>>, _serial: Serial) {
532 self.set_focused(true);
533
534 let mut inner = self.inner.lock().unwrap();
535 for handle in &keys {
536 let key = if let Some(key) = convert_key(handle.raw_syms().iter().copied()) {
537 let modifiers = convert_modifiers(inner.last_modifiers);
538 inner.events.push(Event::Key {
539 key,
540 physical_key: None,
541 pressed: true,
542 repeat: false,
543 modifiers,
544 });
545 Some(key)
546 } else {
547 None
548 };
549 inner.pressed.push((key, handle.raw_code()));
550 if let Some(kbd) = inner.kbd.as_mut() {
551 kbd.key_input(handle.raw_code().raw(), true);
552 }
553 }
554 }
555
556 fn leave(&self, _seat: &Seat<D>, _data: &mut D, _serial: Serial) {
557 self.set_focused(false);
558
559 let keys = std::mem::take(&mut self.inner.lock().unwrap().pressed);
560 let mut inner = self.inner.lock().unwrap();
561 for (key, code) in keys {
562 if let Some(key) = key {
563 let modifiers = convert_modifiers(inner.last_modifiers);
564 inner.events.push(Event::Key {
565 key,
566 physical_key: None,
567 pressed: false,
568 repeat: false,
569 modifiers,
570 });
571 }
572 if let Some(kbd) = inner.kbd.as_mut() {
573 kbd.key_input(code.raw(), false);
574 }
575 }
576 }
577
578 fn key(
579 &self,
580 _seat: &Seat<D>,
581 _data: &mut D,
582 key: KeysymHandle<'_>,
583 state: KeyState,
584 _serial: Serial,
585 _time: u32,
586 ) {
587 let modifiers = self.inner.lock().unwrap().last_modifiers;
588 self.handle_keyboard(&key, state == KeyState::Pressed, modifiers)
589 }
590
591 fn modifiers(
592 &self,
593 _seat: &Seat<D>,
594 _data: &mut D,
595 modifiers: ModifiersState,
596 _serial: Serial,
597 ) {
598 self.inner.lock().unwrap().last_modifiers = modifiers;
599 }
600}
601
602#[cfg(feature = "desktop_integration")]
603impl SpaceElement for EguiState {
604 fn bbox(&self) -> Rectangle<i32, Logical> {
605 self.inner.lock().unwrap().area
606 }
607
608 fn is_in_input_region(&self, point: &Point<f64, Logical>) -> bool {
609 let pos: Point<i32, _> = point.to_i32_round();
610 let last_pos = self.inner.lock().unwrap().last_pointer_position;
611 if (pos.x - last_pos.x) + (pos.y - last_pos.y) < 10 {
612 self.wants_pointer()
613 } else {
614 false
615 }
616 }
617
618 fn set_activate(&self, _activated: bool) {}
619 fn output_enter(&self, _output: &smithay::output::Output, _overlap: Rectangle<i32, Logical>) {}
620 fn output_leave(&self, _output: &smithay::output::Output) {}
621
622 fn z_index(&self) -> u8 {
623 self.inner.lock().unwrap().z_index as u8
624 }
625}