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
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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,24 +199,31 @@ fn view(state: &State) -> Element<'_, Message> {
|
||||
}
|
||||
|
||||
fn body(state: &State) -> Element<'_, Message> {
|
||||
scrollable(
|
||||
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_y(Alignment::Center)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn header(state: &State) -> Element<'_, Message> {
|
||||
row([
|
||||
container(
|
||||
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)
|
||||
.height(Length::Fill)
|
||||
@@ -255,6 +276,7 @@ fn card(item: &Item) -> Element<'_, Message> {
|
||||
.as_ref()
|
||||
.map(|s| s.as_ref())
|
||||
.unwrap_or("Unnamed Item");
|
||||
Button::new(
|
||||
container(
|
||||
column([
|
||||
BlurHash::new(
|
||||
@@ -264,19 +286,24 @@ fn card(item: &Item) -> Element<'_, Message> {
|
||||
.map(|s| s.as_ref())
|
||||
.unwrap_or(""),
|
||||
)
|
||||
.width(200)
|
||||
.height(400)
|
||||
.width(Length::Fill)
|
||||
.height(Length::FillPortion(5))
|
||||
.into(),
|
||||
Text::new(name)
|
||||
.size(16)
|
||||
.align_y(Alignment::Center)
|
||||
.height(Length::FillPortion(1))
|
||||
.into(),
|
||||
Text::new(name).size(16).into(),
|
||||
])
|
||||
.align_x(Alignment::Center)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.padding(70)
|
||||
.width(Length::FillPortion(5))
|
||||
.height(Length::FillPortion(5))
|
||||
.style(container::rounded_box)
|
||||
.width(Length::FillPortion(3))
|
||||
.height(Length::FillPortion(3))
|
||||
.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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user