1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use anyhow::Result;
use image::GrayImage;
use joycon::{
    joycon_sys::mcu::ir::{MCUIRMode, Resolution},
    JoyCon,
};
use pixels::{Pixels, SurfaceTexture};
use winit::{
    dpi::{LogicalPosition, LogicalSize, PhysicalSize},
    event::{Event, VirtualKeyCode},
    event_loop::{ControlFlow, EventLoop},
};
use winit_input_helper::WinitInputHelper;

#[derive(Debug)]
enum Cmd {
    Image(GrayImage),
    Stop,
}

pub fn run(mut joycon: JoyCon) -> Result<()> {
    let res = Resolution::R160x120;
    joycon.enable_ir(res)?;
    joycon.set_ir_image_mode(MCUIRMode::HandAnalysisSilhouetteImage, 255)?;

    let event_loop = EventLoop::with_user_event();

    std::thread::spawn({
        let proxy = event_loop.create_proxy();
        move || -> Result<()> {
            loop {
                let report = joycon.tick()?;
                if let Some(img) = report.image {
                    proxy.send_event(Cmd::Image(img))?;
                }
            }
        }
    });

    let mut input = WinitInputHelper::new();
    let (window, p_width, p_height, mut _hidpi_factor) =
        create_window("Joycon camera", &event_loop);

    let surface_texture = SurfaceTexture::new(p_width, p_height, &window);

    let mut image = None;

    let (p_width, p_height) = (300, 400);
    let mut pixels = Pixels::new(p_width, p_height, surface_texture)?;
    event_loop.run(move |event, _, control_flow| {
        if input.update(&event) {
            // Close events
            if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
                *control_flow = ControlFlow::Exit;
                return;
            }
            // Adjust high DPI factor
            if let Some(factor) = input.scale_factor_changed() {
                _hidpi_factor = factor;
            }
            // Resize the window
            if let Some(size) = input.window_resized() {
                pixels.resize_surface(size.width, size.height);
            }
        } else if let Event::UserEvent(cmd) = event {
            match cmd {
                Cmd::Image(img) => {
                    image = Some(img);
                    window.request_redraw();
                }
                Cmd::Stop => {
                    *control_flow = ControlFlow::Exit;
                    return;
                }
            }
        } else if let Event::RedrawRequested(_) = event {
            let frame = pixels.get_frame();
            frame.fill(0);

            if let Some(ref img) = image {
                for (x, y, pixel) in img.enumerate_pixels() {
                    let offset = ((x + y * p_width) * 4) as usize;
                    let color = &mut frame[offset..offset + 4];
                    color[0] = 0;
                    color[1] = 0;
                    color[2] = pixel.0[0];
                    color[3] = 255;
                }
            }

            if pixels
                .render()
                .map_err(|e| eprintln!("pixels.render() failed: {}", e))
                .is_err()
            {
                *control_flow = ControlFlow::Exit;
                return;
            }
        }
    });
}

const SCREEN_WIDTH: u32 = 600;
const SCREEN_HEIGHT: u32 = 600;

fn create_window(
    title: &str,
    event_loop: &EventLoop<Cmd>,
) -> (winit::window::Window, u32, u32, f64) {
    // Create a hidden window so we can estimate a good default window size
    let window = winit::window::WindowBuilder::new()
        .with_visible(false)
        .with_title(title)
        .build(&event_loop)
        .unwrap();
    let hidpi_factor = window.scale_factor();

    // Get dimensions
    let width = SCREEN_WIDTH as f64;
    let height = SCREEN_HEIGHT as f64;
    let (monitor_width, monitor_height) = {
        if let Some(monitor) = window.current_monitor() {
            let size = monitor.size().to_logical(hidpi_factor);
            (size.width, size.height)
        } else {
            (width, height)
        }
    };
    let scale = (monitor_height / height * 2.0 / 3.0).round().max(1.0);

    // Resize, center, and display the window
    let min_size: winit::dpi::LogicalSize<f64> =
        PhysicalSize::new(width, height).to_logical(hidpi_factor);
    let default_size = LogicalSize::new(width * scale, height * scale);
    let center = LogicalPosition::new(
        (monitor_width - width * scale) / 2.0,
        (monitor_height - height * scale) / 2.0,
    );
    window.set_inner_size(default_size);
    window.set_min_inner_size(Some(min_size));
    window.set_outer_position(center);
    window.set_visible(true);

    let size = default_size.to_physical::<f64>(hidpi_factor);

    (
        window,
        size.width.round() as u32,
        size.height.round() as u32,
        hidpi_factor,
    )
}