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

View File

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