streamdeck-oxide 0.1.0

A high-level framework for creating Stream Deck applications in Rust
Documentation
# StreamDeck Oxide

A high-level framework for creating Stream Deck applications in Rust.

## Features

- Button rendering with text, icons, and custom images
- View system for organizing buttons into screens
- Navigation between views
- Event handling for button presses
- Async support for fetching state and handling actions

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
streamdeck-oxide = "0.1.0"
```

### Other dependencies

`libudev` is required for HID support. You can install it using your package
manager. `flake.nix` with a dev shell is provided for development.

## Usage

```rust
use std::sync::Arc;

use streamdeck_oxide::{
    button::RenderConfig,
    navigation::NavigationEntry,
    run,
    theme::Theme,
    view::{
        customizable::{ClickButton, CustomizableView, ToggleButton},
        View,
    },
    elgato_streamdeck,
    generic_array,
    md_icons,
};

/// Application context for our Stream Deck app
#[derive(Debug, Clone)]
struct AppContext {
    message: String,
}

/// Navigation structure for our Stream Deck app
#[derive(Debug, Clone, Default)]
enum Navigation {
    #[default]
    Main,
    Settings,
}

impl NavigationEntry<generic_array::typenum::U5, generic_array::typenum::U3, AppContext>
    for Navigation
{
    fn get_view(
        &self,
    ) -> Result<
        Box<
            dyn View<
                generic_array::typenum::U5,
                generic_array::typenum::U3,
                AppContext,
                Navigation,
            >,
        >,
        Box<dyn std::error::Error>,
    > {
        match self {
            Navigation::Main => {
                let mut view = CustomizableView::default();
                
                // Add a toggle button
                view.set_button(
                    0,
                    0,
                    ToggleButton::new(
                        "Toggle",
                        Some(md_icons::filled::ICON_HOME),
                        |_ctx: AppContext| async move {
                            println!("Fetching toggle state");
                            // In a real app, you might fetch this from some external state
                            Ok(false)
                        },
                        |ctx: AppContext, state: bool| async move {
                            println!("Toggled state: {}", state);
                            println!("Message: {}", ctx.message);
                            Ok(())
                        },
                    ),
                )?;
                
                // Add a click button
                view.set_button(
                    1,
                    0,
                    ClickButton::new(
                        "Click",
                        Some(md_icons::filled::ICON_TOUCH_APP),
                        |ctx: AppContext| async move {
                            println!("Button clicked!");
                            println!("Message: {}", ctx.message);
                            Ok(())
                        },
                    ),
                )?;
                
                // Add a navigation button to settings
                view.set_navigation(
                    0, 
                    2, 
                    Navigation::Settings, 
                    "Settings", 
                    Some(md_icons::sharp::ICON_SETTINGS)
                )?;

                Ok(Box::new(view))
            }
            Navigation::Settings => {
                let mut view = CustomizableView::default();
                
                // Add some settings buttons
                view.set_button(
                    0, 
                    0, 
                    ClickButton::new(
                        "Option 1",
                        Some(md_icons::filled::ICON_BRIGHTNESS_5),
                        |_ctx| async move {
                            println!("Option 1 selected");
                            Ok(())
                        },
                    )
                )?;
                
                view.set_button(
                    1, 
                    0, 
                    ClickButton::new(
                        "Option 2",
                        Some(md_icons::filled::ICON_VOLUME_UP),
                        |_ctx| async move {
                            println!("Option 2 selected");
                            Ok(())
                        },
                    )
                )?;

                // Add a button that fails to fetch data
                view.set_button(
                    2, 
                    0, 
                    ClickButton::new(
                        "Fail",
                        Some(md_icons::filled::ICON_ERROR),
                        |_ctx| async move {
                            println!("This button will fail");
                            Err("Failed to fetch data".into())
                        },
                    )
                )?;
                
                // Add a navigation button to go back to main
                view.set_navigation(
                    4, 
                    2, 
                    Navigation::Main, 
                    "Back", 
                    Some(md_icons::sharp::ICON_ARROW_BACK)
                )?;

                Ok(Box::new(view))
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("StreamDeck Example Application");
    println!("------------------------------");
    
    // Connect to the Stream Deck
    let hid = elgato_streamdeck::new_hidapi()?;
    let devices = elgato_streamdeck::list_devices(&hid);
    
    println!("Looking for Stream Deck devices...");
    
    let (kind, serial) = devices
        .into_iter()
        .find(|(kind, _)| *kind == elgato_streamdeck::info::Kind::Mk2)
        .ok_or("No Stream Deck found")?;
    
    println!("Found Stream Deck: {:?} ({})", kind, serial);
    
    let deck = Arc::new(elgato_streamdeck::AsyncStreamDeck::connect(&hid, kind, &serial)?);
    println!("Connected to Stream Deck successfully!");
    
    // Create configuration
    let config = RenderConfig::default();
    let theme = Theme::light(); // Use light theme for this example
    
    // Create application context
    let context = AppContext {
        message: "Hello from StreamDeck Example!".to_string(),
    };
    
    println!("Starting Stream Deck application...");
    println!("Press Ctrl+C to exit");

    // Run the application
    run::<Navigation, generic_array::typenum::U5, generic_array::typenum::U3, AppContext>(
        theme, config, deck, context,
    )
    .await?;

    Ok(())
}
```

## Documentation

For more detailed documentation, see the
[API documentation](https://docs.rs/streamdeck-oxide).

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file
for details.

This project is using Roboto font for rendering text. The font files are
included in the `fonts` directory. Roboto is licensed under the Apache License.
See the
[LICENSE-ROBOTO](https://github.com/googlefonts/roboto-2/blob/main/LICENSE) file
for details.