Skip to main content

smithay_egui/
lib.rs

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/// smithay-egui state object
48#[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    /// Creates a new `EguiState`
105    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    /// Retrieve the underlying [`egui::Context`]
139    pub fn context(&self) -> &Context {
140        &self.ctx
141    }
142
143    /// If true, egui is currently listening on text input (e.g. typing text in a TextEdit).
144    pub fn wants_keyboard(&self) -> bool {
145        self.ctx.wants_keyboard_input()
146    }
147
148    /// True if egui is currently interested in the pointer (mouse or touch).
149    /// Could be the pointer is hovering over a Window or the user is dragging a widget.
150    /// If false, the pointer is outside of any egui area and so you may want to forward it to other clients as usual.
151    /// Returns false if a drag started outside of egui and then moved over an egui area.
152    pub fn wants_pointer(&self) -> bool {
153        self.ctx.wants_pointer_input()
154    }
155
156    /// Pass new input devices to `EguiState` for internal tracking
157    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    /// Remove input devices to `EguiState` for internal tracking
164    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    /// Pass keyboard events into `EguiState`.
175    ///
176    /// You do not want to pass in events, egui should not react to, but you need to make sure they add up.
177    /// So for every pressed event, you want to send a released one.
178    ///
179    /// You likely want to use the filter-closure of [`smithay::wayland::seat::KeyboardHandle::input`] to optain these values.
180    /// Use [`smithay::wayland::seat::KeysymHandle`] and the provided [`smithay::wayland::seat::ModifiersState`].
181    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                /* utf8 contains the utf8 string generated by that keystroke
209                 * it can contain 1, multiple characters, or even be empty
210                 */
211                inner.events.push(Event::Text(utf8));
212            }
213        }
214    }
215
216    /// Pass new pointer coordinates to `EguiState`
217    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    /// Pass pointer button presses to `EguiState`
227    ///
228    /// Note: If you are unsure about *which* PointerButtonEvents to send to smithay-egui
229    ///       instead of normal clients, check [`EguiState::wants_pointer`] to figure out,
230    ///       if there is an egui-element below your pointer.
231    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    /// Pass a pointer axis scrolling to `EguiState`
246    ///
247    /// Note: If you are unsure about *which* PointerAxisEvents to send to smithay-egui
248    ///       instead of normal clients, check [`EguiState::wants_pointer`] to figure out,
249    ///       if there is an egui-element below your pointer.
250    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    /// Set if this [`EguiState`] should consider itself focused
264    pub fn set_focused(&self, focused: bool) {
265        self.inner.lock().unwrap().focused = focused;
266    }
267
268    // TODO: touch inputs
269
270    /// Produce a new frame of egui. Returns a [`RenderElement`]
271    ///
272    /// - `ui` is your drawing function
273    /// - `renderer` is a [`GlowRenderer`]
274    /// - `area` limits the space egui will be using and offsets the result
275    /// - `scale` is the scale egui should render in
276    /// - `alpha` applies (additional) transparency to the whole ui
277    /// - `start_time` need to be a fixed point in time before the first `run` call to measure animation-times and the like.
278    /// - `modifiers` should be the current state of modifiers pressed on the keyboards.
279    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()), // TODO query from GlState somehow
357            ..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    /// Sets the z_index as reported by [`SpaceElement::z_index`].
447    ///
448    /// The default is [`RenderZindex::Overlay`].
449    #[cfg(feature = "desktop_integration")]
450    pub fn set_zindex(&self, idx: u8) {
451        self.inner.lock().unwrap().z_index = idx;
452    }
453
454    /// Returns the egui [`PlatformOutput`] generated by the last [`Self::render`] call
455    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        // TODO
492        //self.handle_pointer_axis(frame., y_amount)
493    }
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}