~caolan/bramley

081844115e830525715967e5fe7e821518e3cbad — Caolan McMahon 1 year, 4 months ago 7abd6b3
add input crate to map buttons and chords to input events
6 files changed, 236 insertions(+), 117 deletions(-)

M Cargo.lock
M Cargo.toml
A input/Cargo.toml
A input/src/lib.rs
M menu/Cargo.toml
M menu/src/main.rs
M Cargo.lock => Cargo.lock +10 -0
@@ 333,6 333,15 @@ dependencies = [
]

[[package]]
name = "input"
version = "0.1.0"
dependencies = [
 "buttons",
 "futures",
 "rppal",
]

[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 404,6 413,7 @@ dependencies = [
 "buttons",
 "drawing",
 "futures",
 "input",
 "rpi_memory_display 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "rppal",
 "rusttype",

M Cargo.toml => Cargo.toml +1 -0
@@ 8,4 8,5 @@ members = [
    "buttons",
    "drawing",
    "drawing-macro",
    "input",
]

A input/Cargo.toml => input/Cargo.toml +10 -0
@@ 0,0 1,10 @@
[package]
name = "input"
version = "0.1.0"
authors = ["Caolan McMahon <caolan@caolan.uk>"]
edition = "2018"

[dependencies]
buttons = { path = "../buttons" }
futures = "0.3.5"
rppal = "0.11.3"

A input/src/lib.rs => input/src/lib.rs +137 -0
@@ 0,0 1,137 @@
use futures::stream::Stream;
use futures::task::{Poll, Context};
use buttons::{Button, State};
use std::collections::HashMap;
use std::pin::Pin;

/// The action assigned to a given key press or key chord.
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Input {
    Character(char),
    Backspace,
    Unassigned,
}

pub type Chord = [bool; 6];

// pub struct InputLayers = [Box<

// /// For chorded input layers
// pub struct ChordMap {}

// /// For immediate input layers
// pub struct ImmediateMap {}

/// The input state resulting from a button event.
#[derive(Debug, PartialEq, Clone)]
pub enum InputEvent {
    Preview(Input),
    Commit(Input),
}

/// Holds button states and converts new button events into InputEvents.
pub struct InputEvents<T>
where
    T: Stream<Item=(Button, State)> + Unpin,
{
    /// Button event stream to read from.
    button_events: T,
    /// Map of key chords to Input.
    map: HashMap<Chord, Input>,
    /// Buttons currently in the down state.
    pressed: Chord,
    /// Buttons recently in the down state. Not all buttons will be
    /// released at the same time, so this is useful for reading the
    /// user's intended key chord.
    chord: Chord,
}

impl<T> InputEvents<T>
where
    T: Stream<Item=(Button, State)> + Unpin,
{
    pub fn new(button_events: T, map: HashMap<Chord, Input>) -> Self {
        InputEvents {
            button_events,
            map,
            pressed: [false; 6],
            chord: [false; 6],
        }
    }

    fn update_state(&mut self, button: Button, state: State) {
        match (button, state) {
            (Button::Btn1, State::Down) => {
                self.pressed[0] = true;
                self.chord[0] = true;
            },
            (Button::Btn1, State::Up) => {
                self.pressed[0] = false;
            },
            (Button::Btn2, State::Down) => {
                self.pressed[1] = true;
                self.chord[1] = true;
            },
            (Button::Btn2, State::Up) => {
                self.pressed[1] = false;
            }
            (Button::Btn3, State::Down) => {
                self.pressed[2] = true;
                self.chord[2] = true;
            }
            (Button::Btn3, State::Up) => {
                self.pressed[2] = false;
            }
            (Button::Btn4, State::Down) => {
                self.pressed[3] = true;
                self.chord[3] = true;
            }
            (Button::Btn4, State::Up) => {
                self.pressed[3] = false;
            }
            (Button::Btn5, State::Down) => {
                self.pressed[4] = true;
                self.chord[4] = true;
            }
            (Button::Btn5, State::Up) => {
                self.pressed[4] = false;
            }
            (Button::Btn6, State::Down) => {
                self.pressed[5] = true;
                self.chord[5] = true;
            }
            (Button::Btn6, State::Up) => {
                self.pressed[5] = false;
            },
        };
    }
}

impl<T> Stream for InputEvents<T>
where
    T: Stream<Item=(Button, State)> + Unpin,
{
    type Item = InputEvent;

    fn poll_next(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>
    ) -> Poll<Option<Self::Item>> {
        match Stream::poll_next(Pin::new(&mut self.button_events), cx) {
            Poll::Ready(Some((button, state))) => {
                self.update_state(button, state);
                let input = self.map.get(&self.chord).cloned().unwrap_or(Input::Unassigned);
                let all_released = self.pressed.iter().all(|x| !*x);
                if all_released {
                    // release chord
                    self.chord = [false; 6];
                    Poll::Ready(Some(InputEvent::Commit(input)))
                } else {
                    Poll::Ready(Some(InputEvent::Preview(input)))
                }
            },
            Poll::Pending => Poll::Pending,
            Poll::Ready(None) => Poll::Ready(None),
        }
    }
}

M menu/Cargo.toml => menu/Cargo.toml +2 -1
@@ 10,5 10,6 @@ futures = "0.3.5"
rusttype = "0.9.2"
rppal = "0.11.3"
rpi_memory_display = "0.1.1"
buttons = { path="../buttons" }
drawing = { path="../drawing" }
buttons = { path="../buttons" }
input = { path="../input" }

M menu/src/main.rs => menu/src/main.rs +76 -116
@@ 1,21 1,22 @@
use rpi_memory_display::{MemoryDisplay, MemoryDisplayBuffer, Pixel};
use rppal::spi::{Bus, SlaveSelect};
use rusttype::Font;
use buttons::{self, Button, State};
use input::{InputEvents, InputEvent, Input};
use drawing::{size_of_text, MemoryDisplayBufferExt};
use futures::stream::StreamExt;
use std::process::Command;
// use std::process::Command;
use std::collections::HashMap;

const WIDTH: usize = 400;
const HEIGHT: u8 = 240;
const CS_GPIO: u8 = 12;

fn shutdown() {
    Command::new("sudo")
            .arg("poweroff")
            .status()
            .expect("failed to execute shutdown command");
}
// fn shutdown() {
//     Command::new("sudo")
//             .arg("poweroff")
//             .status()
//             .expect("failed to execute shutdown command");
// }

#[tokio::main]
async fn main() {


@@ 38,13 39,46 @@ async fn main() {
        .expect("Error constructing Font");
    let font_size = 30.0;

    let mut events = buttons::events([16, 20, 21, 25, 24, 23])
        .unwrap()
        .ready_chunks(1000);
    let button_events = buttons::events([16, 20, 21, 25, 24, 23])
        .unwrap();
        // .ready_chunks(1000);

    let mut input_map = HashMap::new();
    input_map.insert([false, false, false, true, false, false], Input::Character(' '));
    input_map.insert([false, false, false, false, true, false], Input::Character('e'));
    input_map.insert([true, false, false, false, false, false], Input::Character('t'));
    input_map.insert([false, true, false, false, false, false], Input::Character('o'));
    input_map.insert([false, false, true, false, false, false], Input::Character('a'));
    input_map.insert([true, false, false, true, false, false], Input::Character('n'));
    input_map.insert([false, true, false, true, false, false], Input::Character('i'));
    input_map.insert([false, false, true, true, false, false], Input::Character('s'));
    input_map.insert([true, false, false, false, true, false], Input::Character('r'));
    input_map.insert([false, true, false, false, true, false], Input::Character('h'));
    input_map.insert([false, false, true, false, true, false], Input::Character('l'));
    input_map.insert([false, false, false, true, true, false], Input::Character('d'));
    input_map.insert([true, true, false, false, false, false], Input::Character('c'));
    input_map.insert([false, true, true, false, false, false], Input::Character('u'));
    input_map.insert([true, false, true, false, false, false], Input::Character('m'));
    input_map.insert([true, false, false, true, true, false], Input::Character('\n'));
    input_map.insert([false, true, false, true, true, false], Input::Character('p'));
    input_map.insert([false, false, true, true, true, false], Input::Character('g'));
    input_map.insert([true, true, false, true, false, false], Input::Character('f'));
    input_map.insert([true, true, false, false, true, false], Input::Character('y'));
    input_map.insert([false, true, true, true, false, false], Input::Character('.'));
    input_map.insert([false, true, true, false, true, false], Input::Character('w'));
    input_map.insert([true, false, true, true, false, false], Input::Character('b'));
    input_map.insert([true, false, true, false, true, false], Input::Character('k'));
    input_map.insert([true, true, true, false, false, false], Input::Character('v'));
    input_map.insert([true, true, false, true, true, false], Input::Character(','));
    input_map.insert([false, true, true, true, true, false], Input::Character('x'));
    input_map.insert([true, false, true, true, true, false], Input::Character('j'));
    input_map.insert([true, true, true, true, false, false], Input::Character('q'));
    input_map.insert([true, true, true, false, true, false], Input::Character('z'));
    input_map.insert([true, true, true, true, true, false], Input::Backspace);

    let mut input_events = InputEvents::new(button_events, input_map).ready_chunks(1000);

    let mut text = String::new();
    let mut chord = [false; 6];
    let mut pressed = [false; 6];

    let draw = |buffer: &mut MemoryDisplayBuffer, text: &str| {
        // clear screen


@@ 77,113 111,39 @@ async fn main() {
    draw(&mut buffer, &text);
    display.update(&buffer).unwrap();

    while let Some(events) = events.next().await {

        // handle all ready events in chunk before drawing
    while let Some(events) = input_events.next().await {
        // catch up with all input events before drawing screen
        for event in events {
            match event {
                (Button::Btn1, State::Down) => {
                    pressed[0] = true;
                    chord[0] = true;
                },
                (Button::Btn1, State::Up) => {
                    pressed[0] = false;
                },
                (Button::Btn2, State::Down) => {
                    pressed[1] = true;
                    chord[1] = true;
                },
                (Button::Btn2, State::Up) => {
                    pressed[1] = false;
                }
                (Button::Btn3, State::Down) => {
                    pressed[2] = true;
                    chord[2] = true;
                }
                (Button::Btn3, State::Up) => {
                    pressed[2] = false;
                }
                (Button::Btn4, State::Down) => {
                    pressed[3] = true;
                    chord[3] = true;
                }
                (Button::Btn4, State::Up) => {
                    pressed[3] = false;
            println!("event: {:?}", event);
            if let InputEvent::Commit(input) = event {
                match input {
                    Input::Backspace => {
                        text.pop();
                    },
                    Input::Character(ch) => {
                        text.push(ch);
                    },
                    Input::Unassigned => {
                        // do nothing
                    },
                }
                (Button::Btn5, State::Down) => {
                    pressed[4] = true;
                    chord[4] = true;
                }
                (Button::Btn5, State::Up) => {
                    pressed[4] = false;
                }
                (Button::Btn6, State::Down) => {
                    pressed[5] = true;
                    chord[5] = true;
                }
                (Button::Btn6, State::Up) => {
                    pressed[5] = false;
                },
            }

            // when all 6 buttons are released check previously
            // pressed buttons to find character to insert
            if pressed.iter().all(|x| !*x) {
                let character = match chord {
                    [true, false, false, false, false, false] => Some('a'),
                    [false, true, false, false, false, false] => Some('e'),
                    [false, true, true, false, false, false] => Some('i'),
                    [true, true, true, false, false, false] => Some('o'),
                    [true, false, true, false, false, false] => Some('u'),
                    [false, false, true, false, false, false] => Some('y'),
                    [false, false, true, false, true, false] => Some('b'),
                    [true, false, true, false, true, false] => Some('c'),
                    [true, true, true, false, true, false] => Some('d'),
                    [false, true, true, false, true, false] => Some('f'),
                    [false, true, false, false, true, false] => Some('g'),
                    [true, true, false, false, true, false] => Some('h'),
                    [true, false, false, false, true, false] => Some('j'),
                    [false, false, false, false, true, false] => Some(' '),
                    [false, false, false, true, true, false] => Some('*'),
                    [true, false, false, true, true, false] => Some('k'),
                    [true, true, false, true, true, false] => Some('l'),
                    [false, true, false, true, true, false] => Some('m'),
                    [false, true, true, true, true, false] => Some('n'),
                    [true, true, true, true, true, false] => Some('p'),
                    [true, false, true, true, true, false] => Some('q'),
                    [false, false, true, true, true, false] => Some('r'),
                    [false, false, true, true, false, false] => Some('s'),
                    [true, false, true, true, false, false] => Some('t'),
                    [true, true, true, true, false, false] => Some('v'),
                    [false, true, true, true, false, false] => Some('w'),
                    [false, true, false, true, false, false] => Some('x'),
                    [true, true, false, true, false, false] => Some('z'),
                    [true, false, false, true, false, false] => Some('-'),
                    [false, false, false, true, false, false] => Some(' '),
                    _ => None
                };
                if let Some(ch) = character {
                    text.push(ch);
                }
                chord = [false; 6];
            }

            // if at any point all 6 buttons are pressed immediately
            // shutdown
            if pressed.iter().all(|x| *x) {
                // buffer.fill(Pixel::White);
                // buffer.draw_text(
                //     &font,
                //     font_size,
                //     &["Shutting down..."],
                //     10,
                //     10,
                // );
                // display.update(&buffer).unwrap();
                shutdown();
                return;
            }
        }
        // // if at any point all 6 buttons are pressed immediately
        // // shutdown
        // if pressed.iter().all(|x| *x) {
        //     // buffer.fill(Pixel::White);
        //     // buffer.draw_text(
        //     //     &font,
        //     //     font_size,
        //     //     &["Shutting down..."],
        //     //     10,
        //     //     10,
        //     // );
        //     // display.update(&buffer).unwrap();
        //     shutdown();
        //     return;
        // }
        draw(&mut buffer, &text);
        // update display (can be slow)
        display.update(&buffer).unwrap();