mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-30 00:41:42 +00:00
Updated UX on image placement
This commit is contained in:
parent
ec7b7088e3
commit
0abd873073
1
assets/expand-image.svg
Normal file
1
assets/expand-image.svg
Normal 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 |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@ -1,7 +1,6 @@
|
|||||||
use super::{GossipUi, NoteData, Page, RepostType};
|
use super::{GossipUi, NoteData, Page, RepostType};
|
||||||
use crate::{feed::FeedKind};
|
use crate::feed::FeedKind;
|
||||||
use crate::globals::GLOBALS;
|
use crate::globals::GLOBALS;
|
||||||
use crate::ui::widgets::break_anywhere_hyperlink_to;
|
|
||||||
use eframe::{
|
use eframe::{
|
||||||
egui::{self, Image, Response},
|
egui::{self, Image, Response},
|
||||||
epaint::Vec2,
|
epaint::Vec2,
|
||||||
@ -174,8 +173,7 @@ pub(super) fn render_content(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContentSegment::Hyperlink(link) => {
|
ContentSegment::Hyperlink(link) => {
|
||||||
let lowercase = link.to_lowercase();
|
if let Some(image_url) = as_image_url(app, &link) {
|
||||||
if let Some(image_url) = as_image_url(app, &lowercase) {
|
|
||||||
show_image_toggle(app, ui, image_url);
|
show_image_toggle(app, ui, image_url);
|
||||||
//} else if is_video_url(&lowercase) {
|
//} else if is_video_url(&lowercase) {
|
||||||
// TODO
|
// 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 {
|
fn is_image_url(url: &str) -> bool {
|
||||||
url.ends_with(".jpg")
|
let lower = url.to_lowercase();
|
||||||
|| url.ends_with(".jpeg")
|
lower.ends_with(".jpg")
|
||||||
|| url.ends_with(".png")
|
|| lower.ends_with(".jpeg")
|
||||||
|| url.ends_with(".gif")
|
|| lower.ends_with(".png")
|
||||||
|| url.ends_with(".webp")
|
|| lower.ends_with(".gif")
|
||||||
|
|| lower.ends_with(".webp")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_image_url(app: &mut GossipUi, url: &str) -> Option<Url> {
|
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 {
|
fn is_video_url(url: &str) -> bool {
|
||||||
url.ends_with(".mov")
|
let lower = url.to_lowercase();
|
||||||
|| url.ends_with(".mp4")
|
lower.ends_with(".mov")
|
||||||
|| url.ends_with(".mkv")
|
|| lower.ends_with(".mp4")
|
||||||
|| url.ends_with(".webm")
|
|| 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 show_link = true;
|
||||||
let mut hovr_response = None;
|
let mut hovr_response = None;
|
||||||
|
|
||||||
let show_image = (app.settings.show_media && !app.media_hide_list.contains(&url)) ||
|
// FIXME show/hide lists should persist app restarts
|
||||||
(!app.settings.show_media && app.media_show_list.contains(&url));
|
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 show_image {
|
||||||
if let Some(response) = try_render_media(app, ui, url.clone()) {
|
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 {
|
if show_link {
|
||||||
let response = ui.link("[ Image ]");
|
let response = ui.link("[ Image ]");
|
||||||
if !app.settings.load_media {
|
if app.settings.load_media {
|
||||||
response.clone().on_hover_text("Setting 'Fetch media' is disabled");
|
response.clone().on_hover_text(url_string.clone());
|
||||||
|
} else {
|
||||||
|
response
|
||||||
|
.clone()
|
||||||
|
.on_hover_text("Setting 'Fetch media' is disabled");
|
||||||
}
|
}
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if app.settings.show_media {
|
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);
|
hovr_response = Some(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from here handle both responses the same, image or link
|
||||||
if let Some(response) = hovr_response {
|
if let Some(response) = hovr_response {
|
||||||
response.context_menu(|ui| {
|
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);
|
let modifiers = ui.ctx().input(|i| i.modifiers);
|
||||||
ui.ctx().output_mut(|o| {
|
ui.ctx().output_mut(|o| {
|
||||||
o.open_url = Some(egui::output::OpenUrl {
|
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);
|
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);
|
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
|
// workaround for egui bug where image enlarges the cursor height
|
||||||
ui.set_row_height(row_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
|
/// Try to fetch and render a piece of media
|
||||||
/// - return: true if successfully rendered, false otherwise
|
/// - return: true if successfully rendered, false otherwise
|
||||||
fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
fn try_render_media(app: &mut GossipUi, ui: &mut Ui, url: Url) -> Option<Response> {
|
||||||
let mut response_return = None;
|
let mut response_return = None;
|
||||||
if let Some(media) = app.try_get_media(ui.ctx(), url) {
|
if let Some(media) = app.try_get_media(ui.ctx(), url.clone()) {
|
||||||
let ui_max = Vec2::new(
|
let ui_max = if app.media_full_width_list.contains(&url) {
|
||||||
ui.available_width(),
|
Vec2::new(
|
||||||
ui.ctx().screen_rect().height() / 4.0,
|
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 msize = media.size_vec2();
|
||||||
let aspect = media.aspect_ratio();
|
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()
|
egui::Frame::none()
|
||||||
.inner_margin(egui::Margin::same(0.0))
|
.inner_margin(egui::Margin::same(0.0))
|
||||||
.outer_margin(egui::Margin {
|
.outer_margin(egui::Margin {
|
||||||
top: ui.available_height(), // line height
|
top: 10.0,
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
right: 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)
|
.rounding(ui.style().noninteractive().rounding)
|
||||||
.stroke(egui::Stroke {
|
|
||||||
width: 1.0,
|
|
||||||
color: egui::Color32::DARK_GRAY,
|
|
||||||
})
|
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
let response = ui.add(Image::new(&media, size).sense(egui::Sense::click()));
|
let response = ui.add(Image::new(&media, size).sense(egui::Sense::click()));
|
||||||
if response.hovered() {
|
if response.hovered() {
|
||||||
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
|
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);
|
response_return = Some(response);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -132,6 +132,7 @@ struct GossipUi {
|
|||||||
about: About,
|
about: About,
|
||||||
icon: TextureHandle,
|
icon: TextureHandle,
|
||||||
placeholder_avatar: TextureHandle,
|
placeholder_avatar: TextureHandle,
|
||||||
|
expand_right_symbol: TextureHandle,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
avatars: HashMap<PublicKeyHex, TextureHandle>,
|
avatars: HashMap<PublicKeyHex, TextureHandle>,
|
||||||
media: HashMap<Url, TextureHandle>,
|
media: HashMap<Url, TextureHandle>,
|
||||||
@ -139,6 +140,8 @@ struct GossipUi {
|
|||||||
media_show_list: HashSet<Url>,
|
media_show_list: HashSet<Url>,
|
||||||
/// used when settings.show_media=false to explicitly hide
|
/// used when settings.show_media=false to explicitly hide
|
||||||
media_hide_list: HashSet<Url>,
|
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
|
||||||
search_result: String,
|
search_result: String,
|
||||||
@ -234,7 +237,7 @@ impl GossipUi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let placeholder_avatar_texture_handle = {
|
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 image = image::load_from_memory(bytes).unwrap();
|
||||||
let size = [image.width() as _, image.height() as _];
|
let size = [image.width() as _, image.height() as _];
|
||||||
let image_buffer = image.to_rgba8();
|
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 current_dpi = (cctx.egui_ctx.pixels_per_point() * 72.0) as u32;
|
||||||
let (override_dpi, override_dpi_value): (bool, u32) = match settings.override_dpi {
|
let (override_dpi, override_dpi_value): (bool, u32) = match settings.override_dpi {
|
||||||
Some(v) => (true, v),
|
Some(v) => (true, v),
|
||||||
@ -281,11 +296,13 @@ impl GossipUi {
|
|||||||
about: crate::about::about(),
|
about: crate::about::about(),
|
||||||
icon: icon_texture_handle,
|
icon: icon_texture_handle,
|
||||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||||
|
expand_right_symbol: expand_right_symbol,
|
||||||
settings,
|
settings,
|
||||||
avatars: HashMap::new(),
|
avatars: HashMap::new(),
|
||||||
media: HashMap::new(),
|
media: HashMap::new(),
|
||||||
media_show_list: HashSet::new(),
|
media_show_list: HashSet::new(),
|
||||||
media_hide_list: HashSet::new(),
|
media_hide_list: HashSet::new(),
|
||||||
|
media_full_width_list: HashSet::new(),
|
||||||
search_result: "".to_owned(),
|
search_result: "".to_owned(),
|
||||||
draft: "".to_owned(),
|
draft: "".to_owned(),
|
||||||
draft_needs_focus: false,
|
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());
|
let unchecked_url = UncheckedUrl(url_string.to_owned());
|
||||||
GLOBALS.media.check_url(unchecked_url)
|
GLOBALS.media.check_url(unchecked_url)
|
||||||
}
|
}
|
||||||
@ -679,11 +695,12 @@ impl GossipUi {
|
|||||||
GLOBALS.media.retry_failed(&url.to_unchecked_url());
|
GLOBALS.media.retry_failed(&url.to_unchecked_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_media(
|
pub fn has_media_loading_failed(&self, url_string: &str) -> bool {
|
||||||
&mut self,
|
let unchecked_url = UncheckedUrl(url_string.to_owned());
|
||||||
ctx: &Context,
|
GLOBALS.media.has_failed(&unchecked_url)
|
||||||
url: Url,
|
}
|
||||||
) -> Option<TextureHandle> {
|
|
||||||
|
pub fn try_get_media(&mut self, ctx: &Context, url: Url) -> Option<TextureHandle> {
|
||||||
// Do not keep retrying if failed
|
// Do not keep retrying if failed
|
||||||
if GLOBALS.media.has_failed(&url.to_unchecked_url()) {
|
if GLOBALS.media.has_failed(&url.to_unchecked_url()) {
|
||||||
return None;
|
return None;
|
||||||
|
Loading…
Reference in New Issue
Block a user