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 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(
|
||||||
|
|||||||
@@ -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,23 +199,30 @@ fn view(state: &State) -> Element<'_, Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn body(state: &State) -> Element<'_, Message> {
|
fn body(state: &State) -> Element<'_, Message> {
|
||||||
container(
|
scrollable(
|
||||||
Grid::with_children(state.cache.items_of(state.current).into_iter().map(card)).spacing(70),
|
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)
|
.height(Length::Fill)
|
||||||
.width(Length::Fill)
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header(state: &State) -> Element<'_, Message> {
|
fn header(state: &State) -> Element<'_, Message> {
|
||||||
row([
|
row([
|
||||||
container(
|
container(
|
||||||
Text::new(state.jellyfin_client.config.server_url.as_str())
|
Button::new(
|
||||||
.width(Length::Fill)
|
Text::new(state.jellyfin_client.config.server_url.as_str())
|
||||||
.align_x(Alignment::Start),
|
.width(Length::Fill)
|
||||||
|
.align_x(Alignment::Start),
|
||||||
|
)
|
||||||
|
.on_press(Message::Home),
|
||||||
)
|
)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
@@ -255,28 +276,34 @@ 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");
|
||||||
container(
|
Button::new(
|
||||||
column([
|
container(
|
||||||
BlurHash::new(
|
column([
|
||||||
item.thumbnail
|
BlurHash::new(
|
||||||
.as_ref()
|
item.thumbnail
|
||||||
.and_then(|t| t.blur_hash.as_ref())
|
.as_ref()
|
||||||
.map(|s| s.as_ref())
|
.and_then(|t| t.blur_hash.as_ref())
|
||||||
.unwrap_or(""),
|
.map(|s| s.as_ref())
|
||||||
)
|
.unwrap_or(""),
|
||||||
.width(200)
|
)
|
||||||
.height(400)
|
.width(Length::Fill)
|
||||||
.into(),
|
.height(Length::FillPortion(5))
|
||||||
Text::new(name).size(16).into(),
|
.into(),
|
||||||
])
|
Text::new(name)
|
||||||
.align_x(Alignment::Center)
|
.size(16)
|
||||||
.width(Length::Fill)
|
.align_y(Alignment::Center)
|
||||||
.height(Length::Fill),
|
.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)
|
.on_press(Message::OpenItem(Some(item.id)))
|
||||||
.width(Length::FillPortion(5))
|
|
||||||
.height(Length::FillPortion(5))
|
|
||||||
.style(container::rounded_box)
|
|
||||||
.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)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user