feat: added placeholder image for images that couldnt be decoded; fixed overflowing titles in mods view (#802)
fix: drop per-render image fallback logging and narrow its memoization cost fix: keep the backdrop fallback, the cache is what fails
This commit is contained in:
parent
ba74bedf92
commit
576c93c96b
7 changed files with 71 additions and 32 deletions
|
|
@ -8,6 +8,7 @@ use oneclient_common::{VersionKey, parse_mc_version};
|
||||||
use crate::AppAssets;
|
use crate::AppAssets;
|
||||||
use crate::hooks::{loaded_image, use_cached_image, use_version_metadata};
|
use crate::hooks::{loaded_image, use_cached_image, use_version_metadata};
|
||||||
use crate::layout::HOME_BACKGROUND_ASSET;
|
use crate::layout::HOME_BACKGROUND_ASSET;
|
||||||
|
use crate::ui::ImageFallbackExt;
|
||||||
|
|
||||||
/// The size cluster cards request so it is usually already cached when anything else wants it
|
/// The size cluster cards request so it is usually already cached when anything else wants it
|
||||||
pub const ART_PREVIEW_EDGE: u32 = 512;
|
pub const ART_PREVIEW_EDGE: u32 = 512;
|
||||||
|
|
@ -121,6 +122,7 @@ impl Component for DynamicArt {
|
||||||
.height(Size::fill())
|
.height(Size::fill())
|
||||||
.aspect_ratio(AspectRatio::Max)
|
.aspect_ratio(AspectRatio::Max)
|
||||||
.image_cover(ImageCover::Center)
|
.image_cover(ImageCover::Center)
|
||||||
|
.fallback(rect().width(Size::fill()).height(Size::fill()))
|
||||||
.into_element()
|
.into_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::components::{Icon, IconType, toggle_controlled};
|
||||||
use crate::hooks::{ClusterAction, loaded_image, use_cached_image, use_cluster_mutation};
|
use crate::hooks::{ClusterAction, loaded_image, use_cached_image, use_cluster_mutation};
|
||||||
use crate::routes::Route;
|
use crate::routes::Route;
|
||||||
use crate::theme::colors;
|
use crate::theme::colors;
|
||||||
use crate::ui::border_all_color;
|
use crate::ui::{ImageFallbackExt, border_all_color};
|
||||||
use crate::utils::format_size;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
pub(crate) const CARD_BG: Color = Color::from_rgb(26, 34, 41);
|
pub(crate) const CARD_BG: Color = Color::from_rgb(26, 34, 41);
|
||||||
|
|
@ -268,6 +268,7 @@ pub(crate) fn grid_card(
|
||||||
.font_size(14.)
|
.font_size(14.)
|
||||||
.font_weight(FontWeight::MEDIUM)
|
.font_weight(FontWeight::MEDIUM)
|
||||||
.max_lines(1)
|
.max_lines(1)
|
||||||
|
.text_overflow(TextOverflow::Ellipsis)
|
||||||
.width(Size::fill())
|
.width(Size::fill())
|
||||||
.color(CARD_NAME.with_a(content_alpha)),
|
.color(CARD_NAME.with_a(content_alpha)),
|
||||||
)
|
)
|
||||||
|
|
@ -277,6 +278,7 @@ pub(crate) fn grid_card(
|
||||||
.text(format!("by {}", item.author))
|
.text(format!("by {}", item.author))
|
||||||
.font_size(10.)
|
.font_size(10.)
|
||||||
.max_lines(1)
|
.max_lines(1)
|
||||||
|
.text_overflow(TextOverflow::Ellipsis)
|
||||||
.width(Size::fill())
|
.width(Size::fill())
|
||||||
.color(colors::fg_secondary().with_a(content_alpha)),
|
.color(colors::fg_secondary().with_a(content_alpha)),
|
||||||
)
|
)
|
||||||
|
|
@ -368,6 +370,7 @@ fn package_info(
|
||||||
.child(
|
.child(
|
||||||
rect()
|
rect()
|
||||||
.horizontal()
|
.horizontal()
|
||||||
|
.width(Size::fill())
|
||||||
.cross_align(Alignment::Center)
|
.cross_align(Alignment::Center)
|
||||||
.spacing(8.)
|
.spacing(8.)
|
||||||
.child(
|
.child(
|
||||||
|
|
@ -376,6 +379,8 @@ fn package_info(
|
||||||
.font_size(15.)
|
.font_size(15.)
|
||||||
.font_weight(FontWeight::MEDIUM)
|
.font_weight(FontWeight::MEDIUM)
|
||||||
.max_lines(1)
|
.max_lines(1)
|
||||||
|
.text_overflow(TextOverflow::Ellipsis)
|
||||||
|
.max_width(Size::percent(60.))
|
||||||
.color(CARD_NAME),
|
.color(CARD_NAME),
|
||||||
)
|
)
|
||||||
.child(if remote {
|
.child(if remote {
|
||||||
|
|
@ -391,6 +396,9 @@ fn package_info(
|
||||||
label()
|
label()
|
||||||
.text(format!("by {}", item.author))
|
.text(format!("by {}", item.author))
|
||||||
.font_size(10.)
|
.font_size(10.)
|
||||||
|
.max_lines(1)
|
||||||
|
.text_overflow(TextOverflow::Ellipsis)
|
||||||
|
.width(Size::fill())
|
||||||
.color(colors::fg_secondary()),
|
.color(colors::fg_secondary()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
@ -400,6 +408,7 @@ fn package_info(
|
||||||
.text(item.description.clone())
|
.text(item.description.clone())
|
||||||
.font_size(11.)
|
.font_size(11.)
|
||||||
.max_lines(2)
|
.max_lines(2)
|
||||||
|
.text_overflow(TextOverflow::Ellipsis)
|
||||||
.width(Size::fill())
|
.width(Size::fill())
|
||||||
.color(colors::fg_secondary()),
|
.color(colors::fg_secondary()),
|
||||||
)
|
)
|
||||||
|
|
@ -422,6 +431,7 @@ pub(crate) fn package_icon(
|
||||||
.height(Size::px(size))
|
.height(Size::px(size))
|
||||||
.aspect_ratio(AspectRatio::Min)
|
.aspect_ratio(AspectRatio::Min)
|
||||||
.corner_radius(CornerRadius::new_all(8.))
|
.corner_radius(CornerRadius::new_all(8.))
|
||||||
|
.fallback(icon_box(IconType::DotsGrid, size))
|
||||||
.into_element(),
|
.into_element(),
|
||||||
|
|
||||||
None if icon_url.is_some() => icon_box(IconType::DotsGrid, size),
|
None if icon_url.is_some() => icon_box(IconType::DotsGrid, size),
|
||||||
|
|
|
||||||
|
|
@ -172,3 +172,14 @@ pub fn entrance_motion_layer(
|
||||||
)
|
)
|
||||||
.into_element()
|
.into_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ImageFallbackExt: Sized {
|
||||||
|
fn fallback(self, placeholder: impl IntoElement) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageFallbackExt for ImageViewer {
|
||||||
|
fn fallback(self, placeholder: impl IntoElement) -> Self {
|
||||||
|
let placeholder = placeholder.into_element();
|
||||||
|
self.error_renderer(move |_: String| placeholder.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::components::{
|
||||||
};
|
};
|
||||||
use crate::hooks::{loaded_image, use_cached_image};
|
use crate::hooks::{loaded_image, use_cached_image};
|
||||||
use crate::theme::colors;
|
use crate::theme::colors;
|
||||||
use crate::ui::border_all_color;
|
use crate::ui::{ImageFallbackExt, border_all_color};
|
||||||
use crate::utils::{format_duration_hm, plural};
|
use crate::utils::{format_duration_hm, plural};
|
||||||
|
|
||||||
use super::{card, card_header};
|
use super::{card, card_header};
|
||||||
|
|
@ -371,6 +371,7 @@ impl Component for ServerIcon {
|
||||||
.height(Size::px(size))
|
.height(Size::px(size))
|
||||||
.aspect_ratio(AspectRatio::Min)
|
.aspect_ratio(AspectRatio::Min)
|
||||||
.corner_radius(CornerRadius::new_all(6.))
|
.corner_radius(CornerRadius::new_all(6.))
|
||||||
|
.fallback(server_icon_placeholder(self.is_ip, size))
|
||||||
.into_element(),
|
.into_element(),
|
||||||
None => server_icon_placeholder(self.is_ip, size),
|
None => server_icon_placeholder(self.is_ip, size),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use oneclient_core::{BundleFileKind, BundleWithUpdateStatus, LinkedArtifactInfo}
|
||||||
use crate::components::{Icon, IconType};
|
use crate::components::{Icon, IconType};
|
||||||
use crate::hooks::{loaded_image, use_cached_image};
|
use crate::hooks::{loaded_image, use_cached_image};
|
||||||
use crate::theme::colors;
|
use crate::theme::colors;
|
||||||
use crate::ui::border_all_color;
|
use crate::ui::{ImageFallbackExt, border_all_color};
|
||||||
|
|
||||||
const BANNER_BG: Color = Color::from_rgb(21, 28, 34);
|
const BANNER_BG: Color = Color::from_rgb(21, 28, 34);
|
||||||
|
|
||||||
|
|
@ -251,29 +251,36 @@ impl Component for Thumbnail {
|
||||||
let query = use_cached_image(self.icon_url.clone(), 256);
|
let query = use_cached_image(self.icon_url.clone(), 256);
|
||||||
let loaded = loaded_image(self.icon_url.as_deref(), &query);
|
let loaded = loaded_image(self.icon_url.as_deref(), &query);
|
||||||
|
|
||||||
|
let placeholder = thumbnail_placeholder(size, radius, 0.4);
|
||||||
|
|
||||||
match loaded {
|
match loaded {
|
||||||
Some((url, bytes)) => ImageViewer::new((url, bytes))
|
Some((url, bytes)) => ImageViewer::new((url, bytes))
|
||||||
.width(Size::px(size))
|
.width(Size::px(size))
|
||||||
.height(Size::px(size))
|
.height(Size::px(size))
|
||||||
.aspect_ratio(AspectRatio::Min)
|
.aspect_ratio(AspectRatio::Min)
|
||||||
.corner_radius(CornerRadius::new_all(radius))
|
.corner_radius(CornerRadius::new_all(radius))
|
||||||
|
.fallback(placeholder)
|
||||||
.into_element(),
|
.into_element(),
|
||||||
None => rect()
|
None => placeholder,
|
||||||
.center()
|
|
||||||
.width(Size::px(size))
|
|
||||||
.height(Size::px(size))
|
|
||||||
.corner_radius(CornerRadius::new_all(radius))
|
|
||||||
.background(colors::component_bg())
|
|
||||||
.child(
|
|
||||||
Icon::new(IconType::DotsGrid)
|
|
||||||
.size(size * 0.4)
|
|
||||||
.color(colors::fg_secondary()),
|
|
||||||
)
|
|
||||||
.into_element(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn thumbnail_placeholder(size: f32, radius: f32, icon_ratio: f32) -> Element {
|
||||||
|
rect()
|
||||||
|
.center()
|
||||||
|
.width(Size::px(size))
|
||||||
|
.height(Size::px(size))
|
||||||
|
.corner_radius(CornerRadius::new_all(radius))
|
||||||
|
.background(colors::component_bg())
|
||||||
|
.child(
|
||||||
|
Icon::new(IconType::DotsGrid)
|
||||||
|
.size(size * icon_ratio)
|
||||||
|
.color(colors::fg_secondary()),
|
||||||
|
)
|
||||||
|
.into_element()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub(crate) struct PackageBanner {
|
pub(crate) struct PackageBanner {
|
||||||
icon_url: Option<String>,
|
icon_url: Option<String>,
|
||||||
|
|
@ -311,6 +318,8 @@ impl Component for PackageBanner {
|
||||||
.overflow(Overflow::Clip)
|
.overflow(Overflow::Clip)
|
||||||
.background(BANNER_BG);
|
.background(BANNER_BG);
|
||||||
|
|
||||||
|
let icon_placeholder = thumbnail_placeholder(icon, 10., 0.45);
|
||||||
|
|
||||||
match loaded {
|
match loaded {
|
||||||
Some((url, bytes)) => banner
|
Some((url, bytes)) => banner
|
||||||
.child(
|
.child(
|
||||||
|
|
@ -324,7 +333,8 @@ impl Component for PackageBanner {
|
||||||
.width(Size::fill())
|
.width(Size::fill())
|
||||||
.height(Size::fill())
|
.height(Size::fill())
|
||||||
.aspect_ratio(AspectRatio::Max)
|
.aspect_ratio(AspectRatio::Max)
|
||||||
.image_cover(ImageCover::Center),
|
.image_cover(ImageCover::Center)
|
||||||
|
.fallback(rect().width(Size::fill()).height(Size::fill())),
|
||||||
)
|
)
|
||||||
.layer(Layer::Relative(1)),
|
.layer(Layer::Relative(1)),
|
||||||
)
|
)
|
||||||
|
|
@ -347,23 +357,12 @@ impl Component for PackageBanner {
|
||||||
.width(Size::px(icon))
|
.width(Size::px(icon))
|
||||||
.height(Size::px(icon))
|
.height(Size::px(icon))
|
||||||
.aspect_ratio(AspectRatio::Min)
|
.aspect_ratio(AspectRatio::Min)
|
||||||
.corner_radius(CornerRadius::new_all(10.)),
|
.corner_radius(CornerRadius::new_all(10.))
|
||||||
|
.fallback(icon_placeholder),
|
||||||
)
|
)
|
||||||
.layer(Layer::Relative(5)),
|
.layer(Layer::Relative(5)),
|
||||||
),
|
),
|
||||||
None => banner.child(
|
None => banner.child(icon_placeholder),
|
||||||
rect()
|
|
||||||
.center()
|
|
||||||
.width(Size::px(icon))
|
|
||||||
.height(Size::px(icon))
|
|
||||||
.corner_radius(CornerRadius::new_all(10.))
|
|
||||||
.background(colors::component_bg())
|
|
||||||
.child(
|
|
||||||
Icon::new(IconType::DotsGrid)
|
|
||||||
.size(icon * 0.45)
|
|
||||||
.color(colors::fg_secondary()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use crate::components::{
|
||||||
};
|
};
|
||||||
use crate::hooks::{loaded_image, query_is_busy, use_cached_image, use_view_state};
|
use crate::hooks::{loaded_image, query_is_busy, use_cached_image, use_view_state};
|
||||||
use crate::theme::colors;
|
use crate::theme::colors;
|
||||||
use crate::ui::{border_all_color, flow_grid};
|
use crate::ui::{ImageFallbackExt, border_all_color, flow_grid};
|
||||||
|
|
||||||
const MAX_COL_W: f32 = 400.;
|
const MAX_COL_W: f32 = 400.;
|
||||||
const GRID_GAP: f32 = 16.;
|
const GRID_GAP: f32 = 16.;
|
||||||
|
|
@ -118,6 +118,7 @@ impl Component for GalleryTile {
|
||||||
.height(Size::fill())
|
.height(Size::fill())
|
||||||
.aspect_ratio(AspectRatio::Max)
|
.aspect_ratio(AspectRatio::Max)
|
||||||
.image_cover(ImageCover::Center)
|
.image_cover(ImageCover::Center)
|
||||||
|
.fallback(broken_image(32.))
|
||||||
.into_element()
|
.into_element()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -202,6 +203,7 @@ impl Component for GalleryViewer {
|
||||||
.width(Size::fill())
|
.width(Size::fill())
|
||||||
.height(Size::fill())
|
.height(Size::fill())
|
||||||
.aspect_ratio(AspectRatio::Min)
|
.aspect_ratio(AspectRatio::Min)
|
||||||
|
.fallback(broken_image(48.))
|
||||||
.into_element()
|
.into_element()
|
||||||
}))
|
}))
|
||||||
// A full-size fetch separate from the tile's cached copy a failed fetch is not loading
|
// A full-size fetch separate from the tile's cached copy a failed fetch is not loading
|
||||||
|
|
@ -265,6 +267,19 @@ impl Component for GalleryViewer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn broken_image(size: f32) -> Element {
|
||||||
|
rect()
|
||||||
|
.center()
|
||||||
|
.width(Size::fill())
|
||||||
|
.height(Size::fill())
|
||||||
|
.child(
|
||||||
|
Icon::new(IconType::FileX02)
|
||||||
|
.size(size)
|
||||||
|
.color(colors::fg_secondary()),
|
||||||
|
)
|
||||||
|
.into_element()
|
||||||
|
}
|
||||||
|
|
||||||
fn chevron_btn(
|
fn chevron_btn(
|
||||||
icon: IconType,
|
icon: IconType,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use oneclient_content::packages::ProviderId;
|
||||||
use crate::components::{Icon, provider_badge};
|
use crate::components::{Icon, provider_badge};
|
||||||
use crate::hooks::{loaded_image, use_cached_image};
|
use crate::hooks::{loaded_image, use_cached_image};
|
||||||
use crate::theme::colors;
|
use crate::theme::colors;
|
||||||
use crate::ui::border_all_color;
|
use crate::ui::{ImageFallbackExt, border_all_color};
|
||||||
use crate::utils::format_size;
|
use crate::utils::format_size;
|
||||||
|
|
||||||
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
|
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
|
||||||
|
|
@ -36,6 +36,7 @@ impl Component for OnboardingModCard {
|
||||||
.height(Size::px(44.))
|
.height(Size::px(44.))
|
||||||
.aspect_ratio(AspectRatio::Min)
|
.aspect_ratio(AspectRatio::Min)
|
||||||
.corner_radius(CornerRadius::new_all(8.))
|
.corner_radius(CornerRadius::new_all(8.))
|
||||||
|
.fallback(icon_box(self.provider))
|
||||||
.into_element(),
|
.into_element(),
|
||||||
|
|
||||||
None => icon_box(self.provider).into_element(),
|
None => icon_box(self.provider).into_element(),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue