feat(ui): enhance BlurHash and navigation functionality
Some checks failed
build / checks-matrix (push) Has been cancelled
build / checks-build (push) Has been cancelled
build / codecov (push) Has been cancelled
docs / docs (push) Has been cancelled

- Modify BlurHash struct to accept iced::Length for dimension
- Add Back and Home navigation messages
- Implement scrollable container and button interactions
This commit is contained in:
uttarayan21
2025-11-19 02:16:47 +05:30
parent a1c36e4fb2
commit 442a7e49b2
2 changed files with 83 additions and 54 deletions

View File

@@ -1,6 +1,6 @@
use std::sync::{Arc, LazyLock, atomic::AtomicBool};
use iced::{Element, advanced::Widget, widget::Image};
use iced::{Element, Length, advanced::Widget, widget::Image};
use crate::shared_string::SharedString;
@@ -8,16 +8,16 @@ use crate::shared_string::SharedString;
pub struct BlurHash {
hash: SharedString,
handle: Arc<iced::advanced::image::Handle>,
width: u32,
height: u32,
width: iced::Length,
height: iced::Length,
punch: f32,
}
impl BlurHash {
pub fn recompute(&mut self) {
let pixels = blurhash::decode(&self.hash, self.width, self.height, self.punch)
.unwrap_or_else(|_| vec![0; (self.width * self.height * 4) as usize]);
let handle = iced::advanced::image::Handle::from_rgba(self.width, self.height, pixels);
pub fn recompute(&mut self, width: u32, height: u32, punch: f32) {
let pixels = blurhash::decode(&self.hash, width, height, punch)
.unwrap_or_else(|_| vec![0; (width * height * 4) as usize]);
let handle = iced::advanced::image::Handle::from_rgba(width, height, pixels);
self.handle = Arc::new(handle);
}
@@ -29,27 +29,24 @@ impl BlurHash {
BlurHash {
hash,
handle,
width: 32,
height: 32,
width: 32.into(),
height: 32.into(),
punch: 1.0,
}
}
pub fn width(mut self, height: u32) -> Self {
self.width = height;
self.recompute();
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = height;
self.recompute();
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
pub fn punch(mut self, punch: f32) -> Self {
self.punch = punch;
self.recompute();
self
}
}
@@ -60,8 +57,8 @@ where
{
fn size(&self) -> iced::Size<iced::Length> {
iced::Size {
width: iced::Length::Fixed(self.width as f32),
height: iced::Length::Fixed(self.height as f32),
width: self.width,
height: self.height,
}
}
@@ -71,17 +68,21 @@ where
renderer: &Renderer,
limits: &iced::advanced::layout::Limits,
) -> iced::advanced::layout::Node {
iced::widget::image::layout(
let layout = iced::widget::image::layout(
renderer,
limits,
&self.handle,
self.width.into(),
self.height.into(),
self.width,
self.height,
None,
iced::ContentFit::default(),
iced::Rotation::default(),
false,
)
);
let height = layout.bounds().height;
let width = layout.bounds().width;
self.recompute(width as u32, height as u32, self.punch);
layout
}
fn draw(

View File

@@ -25,6 +25,7 @@ impl ItemCache {
self.tree.entry(parent).or_default().insert(item.id);
self.items.insert(item.id, item);
}
pub fn extend<I: IntoIterator<Item = Item>>(
&mut self,
parent: impl Into<Option<uuid::Uuid>>,
@@ -98,6 +99,7 @@ struct State {
cache: ItemCache,
jellyfin_client: api::JellyfinClient,
messages: Vec<String>,
history: Vec<Option<uuid::Uuid>>,
}
impl State {
@@ -108,6 +110,7 @@ impl State {
cache: ItemCache::default(),
jellyfin_client,
messages: Vec::new(),
history: Vec::new(),
}
}
}
@@ -120,12 +123,14 @@ pub enum Message {
LoadedItem(Option<uuid::Uuid>, Vec<Item>),
Error(String),
SetToken(String),
Back,
Home,
}
fn update(state: &mut State, message: Message) -> Task<Message> {
match message {
Message::OpenSettings => {
// Foo
// Setting place holder
Task::none()
}
Message::OpenItem(id) => {
@@ -146,6 +151,7 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
}
Message::LoadedItem(id, items) => {
state.cache.extend(id, items);
state.history.push(state.current);
state.current = id;
Task::none()
}
@@ -177,6 +183,14 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
state.jellyfin_client.set_token(token);
Task::none()
}
Message::Back => {
state.current = state.history.pop().unwrap_or(None);
Task::none()
}
Message::Home => {
state.current = None;
Task::done(Message::Refresh)
}
}
}
@@ -185,23 +199,30 @@ fn view(state: &State) -> Element<'_, Message> {
}
fn body(state: &State) -> Element<'_, Message> {
container(
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card)).spacing(70),
scrollable(
container(
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
.spacing(50),
)
.padding(50)
.align_x(Alignment::Center)
// .align_y(Alignment::Center)
.height(Length::Fill)
.width(Length::Fill),
)
.padding(70)
.align_x(Alignment::Center)
// .align_y(Alignment::Center)
.height(Length::Fill)
.width(Length::Fill)
.into()
}
fn header(state: &State) -> Element<'_, Message> {
row([
container(
Text::new(state.jellyfin_client.config.server_url.as_str())
.width(Length::Fill)
.align_x(Alignment::Start),
Button::new(
Text::new(state.jellyfin_client.config.server_url.as_str())
.width(Length::Fill)
.align_x(Alignment::Start),
)
.on_press(Message::Home),
)
.padding(10)
.width(Length::Fill)
@@ -255,28 +276,34 @@ fn card(item: &Item) -> Element<'_, Message> {
.as_ref()
.map(|s| s.as_ref())
.unwrap_or("Unnamed Item");
container(
column([
BlurHash::new(
item.thumbnail
.as_ref()
.and_then(|t| t.blur_hash.as_ref())
.map(|s| s.as_ref())
.unwrap_or(""),
)
.width(200)
.height(400)
.into(),
Text::new(name).size(16).into(),
])
.align_x(Alignment::Center)
.width(Length::Fill)
.height(Length::Fill),
Button::new(
container(
column([
BlurHash::new(
item.thumbnail
.as_ref()
.and_then(|t| t.blur_hash.as_ref())
.map(|s| s.as_ref())
.unwrap_or(""),
)
.width(Length::Fill)
.height(Length::FillPortion(5))
.into(),
Text::new(name)
.size(16)
.align_y(Alignment::Center)
.height(Length::FillPortion(1))
.into(),
])
.align_x(Alignment::Center)
.width(Length::Fill)
.height(Length::Fill),
)
.width(Length::FillPortion(3))
.height(Length::FillPortion(3))
.style(container::rounded_box),
)
.padding(70)
.width(Length::FillPortion(5))
.height(Length::FillPortion(5))
.style(container::rounded_box)
.on_press(Message::OpenItem(Some(item.id)))
.into()
}
@@ -291,7 +318,8 @@ fn init(config: impl Fn() -> api::JellyfinConfig + 'static) -> impl Fn() -> (Sta
Ok(token) => Message::SetToken(token),
Err(e) => Message::Error(format!("Authentication failed: {}", e)),
},
),
)
.chain(Task::done(Message::Refresh)),
)
}
}