Updated UX on image placement

This commit is contained in:
Nethanja Focking 2023-04-06 15:18:11 -06:00
parent ec7b7088e3
commit 0abd873073
4 changed files with 86 additions and 43 deletions

1
assets/expand-image.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.54 37.3"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polyline points="0.42 0.27 11.95 18.65 0.42 37.04" style="fill:none;stroke:#878787;stroke-miterlimit:10"/></g></g></svg>

After

Width:  |  Height:  |  Size: 298 B

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,7 +1,6 @@
use super::{GossipUi, NoteData, Page, RepostType};
use crate::{feed::FeedKind};
use crate::feed::FeedKind;
use crate::globals::GLOBALS;
use crate::ui::widgets::break_anywhere_hyperlink_to;
use eframe::{
egui::{self, Image, Response},
epaint::Vec2,
@ -174,8 +173,7 @@ pub(super) fn render_content(
}
}
ContentSegment::Hyperlink(link) => {
let lowercase = link.to_lowercase();
if let Some(image_url) = as_image_url(app, &lowercase) {
if let Some(image_url) = as_image_url(app, &link) {
show_image_toggle(app, ui, image_url);
//} else if is_video_url(&lowercase) {
// TODO
@ -224,11 +222,12 @@ fn render_event_link(app: &mut GossipUi, ui: &mut Ui, note: &NoteData, id: &Id)
}
fn is_image_url(url: &str) -> bool {
url.ends_with(".jpg")
|| url.ends_with(".jpeg")
|| url.ends_with(".png")
|| url.ends_with(".gif")
|| url.ends_with(".webp")
let lower = url.to_lowercase();
lower.ends_with(".jpg")
|| lower.ends_with(".jpeg")
|| lower.ends_with(".png")
|| lower.ends_with(".gif")
|| lower.ends_with(".webp")
}
fn as_image_url(app: &mut GossipUi, url: &str) -> Option<Url> {
@ -241,10 +240,11 @@ fn as_image_url(app: &mut GossipUi, url: &str) -> Option<Url> {
/*
fn is_video_url(url: &str) -> bool {
url.ends_with(".mov")
|| url.ends_with(".mp4")
|| url.ends_with(".mkv")
|| url.ends_with(".webm")
let lower = url.to_lowercase();
lower.ends_with(".mov")
|| lower.ends_with(".mp4")
|| lower.ends_with(".mkv")
|| lower.ends_with(".webm")
}
*/
@ -254,8 +254,9 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
let mut show_link = true;
let mut hovr_response = None;
let show_image = (app.settings.show_media && !app.media_hide_list.contains(&url)) ||
(!app.settings.show_media && app.media_show_list.contains(&url));
// FIXME show/hide lists should persist app restarts
let show_image = (app.settings.show_media && !app.media_hide_list.contains(&url))
|| (!app.settings.show_media && app.media_show_list.contains(&url));
if show_image {
if let Some(response) = try_render_media(app, ui, url.clone()) {
@ -273,8 +274,12 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
if show_link {
let response = ui.link("[ Image ]");
if !app.settings.load_media {
response.clone().on_hover_text("Setting 'Fetch media' is disabled");
if app.settings.load_media {
response.clone().on_hover_text(url_string.clone());
} else {
response
.clone()
.on_hover_text("Setting 'Fetch media' is disabled");
}
if response.clicked() {
if app.settings.show_media {
@ -286,9 +291,10 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
hovr_response = Some(response);
}
// from here handle both responses the same, image or link
if let Some(response) = hovr_response {
response.context_menu(|ui| {
if ui.button("open in browser").clicked() {
if ui.button("Open in browser").clicked() {
let modifiers = ui.ctx().input(|i| i.modifiers);
ui.ctx().output_mut(|o| {
o.open_url = Some(egui::output::OpenUrl {
@ -297,10 +303,10 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
});
});
}
if ui.button("copy URL").clicked() {
if ui.button("Copy URL").clicked() {
ui.output_mut(|o| o.copied_text = url_string);
}
if ui.button("try reload").clicked() {
if ui.button("Try reload ...").clicked() {
app.retry_media(&url);
}
});
@ -310,20 +316,24 @@ fn show_image_toggle(app: &mut GossipUi, ui: &mut Ui, url: Url) {
// workaround for egui bug where image enlarges the cursor height
ui.set_row_height(row_height);
// now show a small hyperlink to the image
// break_anywhere_hyperlink_to(ui, RichText::new(url_string.clone()).small(), url_string);
}
/// Try to fetch and render a piece of media
/// - return: true if successfully rendered, false otherwise
fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
let mut response_return = None;
if let Some(media) = app.try_get_media(ui.ctx(), url) {
let ui_max = Vec2::new(
ui.available_width(),
ui.ctx().screen_rect().height() / 4.0,
);
if let Some(media) = app.try_get_media(ui.ctx(), url.clone()) {
let ui_max = if app.media_full_width_list.contains(&url) {
Vec2::new(
ui.available_width() * 0.9,
ui.ctx().screen_rect().height() * 0.9,
)
} else {
Vec2::new(
ui.available_width() / 2.0,
ui.ctx().screen_rect().height() / 3.0,
)
};
let msize = media.size_vec2();
let aspect = media.aspect_ratio();
@ -362,22 +372,37 @@ fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Respons
egui::Frame::none()
.inner_margin(egui::Margin::same(0.0))
.outer_margin(egui::Margin {
top: ui.available_height(), // line height
top: 10.0,
left: 0.0,
right: 0.0,
bottom: ui.available_height(), // line height
bottom: 10.0,
})
.fill(egui::Color32::GRAY)
.fill(egui::Color32::TRANSPARENT)
.rounding(ui.style().noninteractive().rounding)
.stroke(egui::Stroke {
width: 1.0,
color: egui::Color32::DARK_GRAY,
})
.show(ui, |ui| {
let response = ui.add(Image::new(&media, size).sense(egui::Sense::click()));
if response.hovered() {
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
}
let extend_area = egui::Rect{ min: response.rect.right_top(), max: response.rect.right_bottom() + egui::Vec2::new(20.0,0.0) };
if let Some(pointer_pos) = ui.ctx().pointer_latest_pos() {
if extend_area.contains( pointer_pos ) {
if ui
.add(
egui::Button::new( if app.media_full_width_list.contains(&url) { "<" } else { ">" })
.fill(egui::Color32::TRANSPARENT)
.min_size(Vec2::new(20.0, ui.available_height())),
)
.clicked()
{
if app.media_full_width_list.contains(&url) {
app.media_full_width_list.remove(&url);
} else {
app.media_full_width_list.insert(url.clone());
}
}
}
}
response_return = Some(response);
});
};

View File

@ -132,6 +132,7 @@ struct GossipUi {
about: About,
icon: TextureHandle,
placeholder_avatar: TextureHandle,
expand_right_symbol: TextureHandle,
settings: Settings,
avatars: HashMap<PublicKeyHex, TextureHandle>,
media: HashMap<Url, TextureHandle>,
@ -139,6 +140,8 @@ struct GossipUi {
media_show_list: HashSet<Url>,
/// used when settings.show_media=false to explicitly hide
media_hide_list: HashSet<Url>,
/// media that the user has selected to show full-width
media_full_width_list: HashSet<Url>,
// Search result
search_result: String,
@ -234,7 +237,7 @@ impl GossipUi {
};
let placeholder_avatar_texture_handle = {
let bytes = include_bytes!("../../placeholder_avatar.png");
let bytes = include_bytes!("../../assets/placeholder_avatar.png");
let image = image::load_from_memory(bytes).unwrap();
let size = [image.width() as _, image.height() as _];
let image_buffer = image.to_rgba8();
@ -246,6 +249,18 @@ impl GossipUi {
)
};
let expand_right_symbol = {
let bytes = include_bytes!("../../assets/expand-image.svg");
let color_image = egui_extras::image::load_svg_bytes_with_size(
bytes,
egui_extras::image::FitTo::Size(200, 1000),
).unwrap();
cctx.egui_ctx.load_texture(
"expand_right_symbol",
color_image,
TextureOptions::default())
};
let current_dpi = (cctx.egui_ctx.pixels_per_point() * 72.0) as u32;
let (override_dpi, override_dpi_value): (bool, u32) = match settings.override_dpi {
Some(v) => (true, v),
@ -281,11 +296,13 @@ impl GossipUi {
about: crate::about::about(),
icon: icon_texture_handle,
placeholder_avatar: placeholder_avatar_texture_handle,
expand_right_symbol: expand_right_symbol,
settings,
avatars: HashMap::new(),
media: HashMap::new(),
media_show_list: HashSet::new(),
media_hide_list: HashSet::new(),
media_full_width_list: HashSet::new(),
search_result: "".to_owned(),
draft: "".to_owned(),
draft_needs_focus: false,
@ -669,8 +686,7 @@ impl GossipUi {
}
}
pub fn try_check_url(&self, url_string: &str) -> Option<Url>
{
pub fn try_check_url(&self, url_string: &str) -> Option<Url> {
let unchecked_url = UncheckedUrl(url_string.to_owned());
GLOBALS.media.check_url(unchecked_url)
}
@ -679,11 +695,12 @@ impl GossipUi {
GLOBALS.media.retry_failed(&url.to_unchecked_url());
}
pub fn try_get_media(
&mut self,
ctx: &Context,
url: Url,
) -> Option<TextureHandle> {
pub fn has_media_loading_failed(&self, url_string: &str) -> bool {
let unchecked_url = UncheckedUrl(url_string.to_owned());
GLOBALS.media.has_failed(&unchecked_url)
}
pub fn try_get_media(&mut self, ctx: &Context, url: Url) -> Option<TextureHandle> {
// Do not keep retrying if failed
if GLOBALS.media.has_failed(&url.to_unchecked_url()) {
return None;