14 unstable releases (3 breaking)
Uses new Rust 2024
| 0.4.0 | Feb 15, 2026 |
|---|---|
| 0.3.1 | Feb 12, 2026 |
| 0.2.3 | Feb 10, 2026 |
| 0.1.8 | Feb 9, 2026 |
#1533 in GUI
32KB
409 lines
About
Iced Spatial Navigation is an unofficial extension to the Iced UI framework.
It is designed to bring easy remote or gamepad navigation to any TV/console style app.
Features
It is also designed to scale well with any display, completely lossless.
It should be able to handle any vertical button grid you can throw at it, with scrolling and all.
Fair warning, though: it cannot handle typical horizontal scrolling without slight code modification.
Why did I make this?
I was wanting to make a Linux smart TV distro, but no matter which one I went to,
at least for Plasma 6, Plasma Bigscreen wasn't available.
And Kodi smart TV projects were already available (such as LibreELEC).
SteamOS can do something similar, but it's more clunky and made for, you know, gaming.
So, I figured, why not try something like this?
I'd learnt a fair bit in the process anyway, so even if this amounts to nothing on my end,
it didn't really go to waste.
Setup
In order to set up Iced Spatial Navigation in your project, simply type:
cargo add iced_spatial_navigation
Or, in your Cargo.toml file:
iced_spatial_navigation = { git = "https://github.com/JaydonXOneGitHub/iced_spatial_navigation", branch = "master" }
Use in Iced application
Set it up in a few easy steps.
In main, you can keep it as-is:
fn main() -> Result<(), iced::Error> {
iced::application(initialize, update, view)
.subscription(subscription)
.scale_factor(Environment::get_scale_factor)
.run()
}
However, here's where it's probably going to get a little weird.
In main.rs, or another file, make a custom enum:
pub enum CustomMessage { /* Fill in fields */
Make sure to implement Clone for it, too, though.
Similarly, define a GridButton struct that implements TGridButton:
pub struct GridButton {}
impl TGridButton for GridButton {
// How exactly it's done is up to you.
}
Then, use this as the initialize function:
fn initialize() -> Environment<CustomMessage, GridButton> { // Make sure these custom types are accessible from here
Environment::new(|| Grid::new().with_tile_size(150.0).with_spacing(10.0)) // Do more with this if you want to preemptively change the Grid object
}
Or this:
fn initialize() -> (Environment<CustomMessage, GridButton>, Task<Message<CustomMessage>>) { // Make sure these custom types are accessible from here
let env = Environment::new(|| Grid::new().with_tile_size(150.0).with_spacing(10.0).with_padding(10.0)); // Do more with this if you want to preemptively change the Grid object
let task = Task::none();
(env, task)
}
update and view should look something like this:
fn update(_env: &mut Environment<CustomMessage, GridButton>, msg: Message<CustomMessage>) -> Task<Message<CustomMessage>> {
match msg {
Message::Event(ev) => {
Task::none() // Replace this with your own event function call
},
Message::ButtonPressed(_pos) => {
Task::none() // Replace this with your own function call if needed
},
Message::ItemSelected(_id) => {
Task::none() // Replace this with your own function call if needed
},
Message::Navigate(dir) => {
_env.get_grid_mut().move_on_grid(dir)
}
_ => Task::none()
}
}
fn view(_env: &Environment) -> Element<'_, Message<CustomMessage>> {
row![
_env.get_grid().to_element()
].into()
}
As for subscription:
fn subscription(_env: &Environment<CustomMessage, GridButton>) -> Subscription<Message<CustomMessage>> {
iced::event::listen().map(Message::Event) // Maps to standard Iced events
}
And I do believe that's everything needed done to set it up.
Notice
If you find problems with the library, fork it.
I trust that you'll be able to do well and fix the issues. 😊
Dependencies
~29–49MB
~768K SLoC