easy-imgui-window 0.23.0

Default windowing for the easy-imgui crate using winit and easy-imgui-renderer
Documentation
/*!
 * An example of how to use external events in an `easy-imgui` loop.
 * This program uses the crate `gilrs` to read gamepad/joystick events.
 */
use std::time::Duration;

use easy_imgui::{Color, DrawFlags, UiBuilder, Vector2, WindowFlags, lbl_id, vec2};
use easy_imgui_window::{
    AppEvent, AppHandler, Application, Args, EventLoopExt, EventResult, winit,
};
use winit::{
    event::WindowEvent,
    event_loop::{EventLoop, EventLoopProxy},
    window::Window,
};

use anyhow::Result;

fn main() {
    let event_loop = EventLoop::with_user_event().build().unwrap();

    let mut main = AppHandler::<MyApp>::new(&event_loop, ());

    let proxy = main.event_proxy().clone();
    std::thread::spawn(move || run_input_events(proxy));

    *main.attributes() = Window::default_attributes().with_title("Gamepad");

    event_loop.run_app(&mut main).unwrap();
    /*
    event_loop
        .run(move |event, w| {
            let res = window.do_event(&mut app, &event);
            if res.window_closed {
                w.exit();
            }
            if let winit::event::Event::UserEvent(e) = event {
                window.ping_user_input();
                app.update_gamepad(e);
            }
        })
        .unwrap();
    */
}

#[derive(Debug)]
struct MyGamepadInfo {
    id: gilrs::GamepadId,
    name: String,
}
#[derive(Debug)]
enum MyEvent {
    GamepadConnected(MyGamepadInfo),
    GamepadEvent(gilrs::Event),
}

#[derive(Debug)]
struct MyApp {
    demo: bool,
    connected: Option<MyGamepadInfo>,
    btn: [bool; 8],
    axis: [f32; 4],
}

impl MyApp {
    fn new() -> Self {
        MyApp {
            demo: false,
            connected: None,
            btn: [false; 8],
            axis: [0.0; 4],
        }
    }
    fn b_idx(b: gilrs::Button) -> Option<usize> {
        let r = match b {
            gilrs::Button::South => 0,
            gilrs::Button::East => 1,
            gilrs::Button::North => 2,
            gilrs::Button::West => 3,
            gilrs::Button::DPadDown => 4,
            gilrs::Button::DPadRight => 5,
            gilrs::Button::DPadUp => 6,
            gilrs::Button::DPadLeft => 7,
            _ => return None,
        };
        Some(r)
    }
    fn a_idx(a: gilrs::Axis) -> Option<usize> {
        let r = match a {
            gilrs::Axis::LeftStickX => 0,
            gilrs::Axis::LeftStickY => 1,
            gilrs::Axis::RightStickX => 2,
            gilrs::Axis::RightStickY => 3,
            _ => return None,
        };
        Some(r)
    }
    fn update_gamepad(&mut self, e: MyEvent) {
        match e {
            MyEvent::GamepadConnected(g) => {
                if self.connected.is_none() {
                    self.connected = Some(g);
                }
            }
            MyEvent::GamepadEvent(e) => match e.event {
                gilrs::EventType::ButtonPressed(b, _) => {
                    if let Some(i) = Self::b_idx(b) {
                        self.btn[i] = true;
                    }
                    if matches!(
                        b,
                        gilrs::Button::Select | gilrs::Button::Mode | gilrs::Button::Start
                    ) {
                        self.demo ^= true;
                    }
                }
                gilrs::EventType::ButtonReleased(b, _) => {
                    if let Some(i) = Self::b_idx(b) {
                        self.btn[i] = false;
                    }
                }
                gilrs::EventType::AxisChanged(a, v, _) => {
                    if let Some(i) = Self::a_idx(a) {
                        self.axis[i] = v;
                    }
                }
                gilrs::EventType::Disconnected => {
                    if matches!(&self.connected, Some(t) if t.id == e.id) {
                        self.connected = None;
                        self.btn = [false; 8];
                        self.axis = [0.0; 4];
                    }
                }
                _ => {}
            },
        }
    }
}

impl Application for MyApp {
    type UserEvent = MyEvent;
    type Data = ();

    fn new(_: Args<Self>) -> MyApp {
        MyApp::new()
    }
    fn window_event(&mut self, args: Args<Self>, _event: WindowEvent, res: EventResult) {
        if res.window_closed {
            args.event_loop.exit();
        }
    }
    fn user_event(&mut self, mut args: Args<Self>, event: MyEvent) {
        args.ping_user_input();
        self.update_gamepad(event);
    }
}

impl UiBuilder for MyApp {
    fn do_ui(&mut self, ui: &easy_imgui::Ui<Self>) {
        if self.demo {
            ui.show_demo_window(Some(&mut self.demo));
        }

        ui.window_config(lbl_id(
            format!(
                "Gamepad: {}",
                self.connected
                    .as_ref()
                    .map(|info| info.name.as_str())
                    .unwrap_or("disconnected")
            ),
            "gamepad",
        ))
        .flags(WindowFlags::AlwaysAutoResize)
        .with(|| {
            let p0 = ui.get_cursor_screen_pos();
            let sz = ui.get_content_region_avail();
            let p1 = vec2(p0.x + sz.x, p0.y + sz.y);
            ui.dummy(vec2(340.0, 260.0));

            let dr = ui.window_draw_list();
            dr.add_rect_filled(p0, p1, Color::new(1.0, 1.0, 1.0, 1.0), 0.0, DrawFlags::None);
            dr.add_rect(
                p0,
                p1,
                Color::new(0.5, 0.5, 0.5, 1.0),
                0.0,
                4.0,
                DrawFlags::None,
            );

            static BUTTONS: &[[f32; 2]] = &[
                [250.0, 80.0 + 30.0],
                [250.0 + 30.0, 80.0],
                [250.0, 80.0 - 30.0],
                [250.0 - 30.0, 80.0],
                [80.0, 180.0 + 30.0],
                [80.0 + 30.0, 180.0],
                [80.0, 180.0 - 30.0],
                [80.0 - 30.0, 180.0],
            ];
            let color = if self.connected.is_some() {
                Color::new(0.75, 0.1, 0.2, 1.0)
            } else {
                Color::new(0.75, 0.75, 0.75, 1.0)
            };
            let draw_btn = |center: Vector2, color, dpad, filled| {
                if dpad {
                    let tl = vec2(center.x - 10.0, center.y - 10.0);
                    let br = vec2(center.x + 10.0, center.y + 10.0);
                    if filled {
                        dr.add_rect_filled(tl, br, color, 2.0, DrawFlags::RoundCornersAll);
                    } else {
                        dr.add_rect(tl, br, color, 2.0, 2.0, DrawFlags::RoundCornersAll);
                    }
                } else if filled {
                    dr.add_circle_filled(center, 10.0, color, 0);
                } else {
                    dr.add_circle(center, 10.0, color, 0, 2.0);
                }
            };
            for (idx, pos) in BUTTONS.iter().enumerate() {
                let dpad = idx >= 4;
                draw_btn(
                    vec2(p0.x + pos[0], p0.y + pos[1]),
                    color,
                    dpad,
                    self.btn[idx],
                );
            }
            static AXES: &[[f32; 2]] = &[[80.0, 80.0], [250.0, 180.0]];
            const R1: f32 = 40.0;
            const R2: f32 = 15.0;
            for (idx, pos) in AXES.iter().enumerate() {
                let x = self.axis[2 * idx];
                let y = self.axis[2 * idx + 1];
                dr.add_circle_filled(
                    vec2(p0.x + pos[0], p0.y + pos[1]),
                    R1 + R2,
                    Color::new(0.8, 0.8, 0.8, 1.0),
                    0,
                );
                dr.add_circle_filled(
                    vec2(p0.x + pos[0] + x * R1, p0.y + pos[1] - y * R1),
                    R2,
                    color,
                    0,
                );
            }
        });
    }
}

fn run_input_events(proxy: EventLoopProxy<AppEvent<MyApp>>) -> Result<()> {
    let mut gilrs = gilrs::Gilrs::new().unwrap();
    for (id, gamepad) in gilrs.gamepads() {
        let name = gamepad.name();
        println!("{} is {:?}", name, gamepad.power_info());
        proxy.send_user(MyEvent::GamepadConnected(MyGamepadInfo {
            id,
            name: name.to_owned(),
        }))?;
    }
    loop {
        while let Some(e) = gilrs.next_event_blocking(Some(Duration::from_secs(3600))) {
            if e.event == gilrs::EventType::Connected {
                let pad = gilrs.gamepad(e.id);
                let name = pad.name();
                proxy.send_user(MyEvent::GamepadConnected(MyGamepadInfo {
                    id: e.id,
                    name: name.to_owned(),
                }))?;
            } else {
                proxy.send_user(MyEvent::GamepadEvent(e))?;
            }
        }
    }
}