From 442a7e49b22118db31e02683f69c6d2c061356ed Mon Sep 17 00:00:00 2001 From: uttarayan21 Date: Wed, 19 Nov 2025 02:16:47 +0530 Subject: [PATCH] feat(ui): enhance BlurHash and navigation functionality - Modify BlurHash struct to accept iced::Length for dimension - Add Back and Home navigation messages - Implement scrollable container and button interactions --- ui-iced/src/blur_hash.rs | 45 ++++++++++---------- ui-iced/src/lib.rs | 92 ++++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 54 deletions(-) diff --git a/ui-iced/src/blur_hash.rs b/ui-iced/src/blur_hash.rs index 86d75f3..2ae3cc2 100644 --- a/ui-iced/src/blur_hash.rs +++ b/ui-iced/src/blur_hash.rs @@ -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, - 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) -> 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) -> 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::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( diff --git a/ui-iced/src/lib.rs b/ui-iced/src/lib.rs index 7460254..416beac 100644 --- a/ui-iced/src/lib.rs +++ b/ui-iced/src/lib.rs @@ -25,6 +25,7 @@ impl ItemCache { self.tree.entry(parent).or_default().insert(item.id); self.items.insert(item.id, item); } + pub fn extend>( &mut self, parent: impl Into>, @@ -98,6 +99,7 @@ struct State { cache: ItemCache, jellyfin_client: api::JellyfinClient, messages: Vec, + history: Vec>, } 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, Vec), Error(String), SetToken(String), + Back, + Home, } fn update(state: &mut State, message: Message) -> Task { 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::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 { 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)), ) } }