Crate btnify

Source
Expand description

§Examples

Hello World

use btnify::button::{Button, ButtonResponse};

fn greet_handler() -> ButtonResponse {
    ButtonResponse::from("hello world!")
}

// this button doesn't use any state so we will mark the state generic as unit
let greet_button: Button<()> = Button::create_basic_button("Greet!", Box::new(greet_handler));

Hello World 2.0

use btnify::button::{Button, ButtonResponse};

fn better_greet_handler(responses: Vec<Option<String>>) -> ButtonResponse {
    // responses is guaranteed to be the same length as the number of extra prompts
    // specified when creating a button
    let name = &responses[0];
    match name {
        Some(name) => format!("Hello, {name}").into(),
        None => format!("You didn't provide a name! :(").into()
    }
}

let better_greet_button: Button<()> = Button::create_button_with_prompts(
    "Greet 2.0",
    Box::new(better_greet_handler),
    vec!["What's your name?".to_string()]
);

Counter App

use std::sync::Mutex;
use tokio::sync::oneshot;
use btnify::bind_server;
use btnify::ShutdownConfig;
use btnify::button::{Button, ButtonResponse};

struct Counter {
    // must use Mutex for interior mutability and thread-safety
    count: Mutex<i32>,
    end_server_tx: Mutex<Option<oneshot::Sender<()>>>,
}

impl Counter {
    fn new(tx: oneshot::Sender<()>) -> Counter {
        Counter {
            count: Mutex::new(0),
            end_server_tx: Mutex::new(Some(tx)),
        }
    }

    fn end_server(&self) {
        // Acquire the Mutex to modify
        let mut tx = self.end_server_tx.lock().unwrap();

        // Take the sender
        let tx = tx.take().unwrap();

        // Send the signal to end the server
        tx.send(()).unwrap();
    }
}

fn count_handler(state: &Counter) -> ButtonResponse {
    let count  = state.count.lock().unwrap();
    format!("The count is: {count}").into()
}

fn plus_handler(state: &Counter, responses: Vec<Option<String>>) -> ButtonResponse {
    match &responses[0] {
        Some(response_str) => {
            if let Ok(amount) = response_str.parse::<i32>() {
                let mut count = state.count.lock().unwrap();
                *count += amount;
                format!("The count now is: {}", *count).into()
            } else {
                "You did not provide a number.".into()
            }
        }
        None => "You didn't provide any input.".into(),
    }
}

fn end_button_handler(state: &Counter) -> ButtonResponse {
    state.end_server();
    "Server is ending. Goodbye!".into()
}

fn server_end(state: &Counter) {
    println!("goodbye world. ;(");
}

let count_button = Button::create_button_with_state("Counter", Box::new(count_handler));

let plus_button = Button::create_button_with_state_and_prompts(
    "add to counter",
    Box::new(plus_handler),
    vec!["How much do you want to add?".to_string()]
);

let end_button = Button::create_button_with_state("End Server", Box::new(end_button_handler));

let buttons = vec![count_button, plus_button, end_button];

let (tx, rx) = oneshot::channel();

let shutdown_config = ShutdownConfig::new(Some(rx), Some(Box::new(server_end)));

bind_server(&"0.0.0.0:3000".parse().unwrap(), buttons, Counter::new(tx), None);
// uncomment to actually run the server:
//    .await
//    .unwrap();

Modules§

button
oneshot
A one-shot channel is used for sending a single message between asynchronous tasks. The channel function is used to create a Sender and Receiver handle pair that form the channel.

Structs§

ShutdownConfig
When the Btnify server is about to shut down the specified handler will be called. The server will be triggered to shut down when either ctrl+c is pressed or the shutdown_rx receiver is triggered. I recommended that you store the sender in your server’s state.

Functions§

bind_server
Start your btnify server on the specified address with the specified Buttons and state, along with the specified ShutdownConfig. If you don’t need any custom state then use a unit (())