use super::*;
#[cfg(feature = "gui")]
use egui::Button;
#[cfg(feature = "gui")]
use mkutil::finder;
#[cfg(feature = "image_processing")]
const MAX_TALL_IMAGE_RATIO: f32 = 1.15;
#[derive(Debug)]
pub struct ImageLink {
url: Option<PathBuf>,
#[cfg(feature = "gui")]
button: egui::ImageButton,
}
#[cfg(feature = "gui")]
impl egui::Widget for ImageLink {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
let ImageLink { url, button } = self;
let response = button.ui(ui);
if let Some(url) = url {
if response.clicked() {
if let Err(e) = finder::reveal(&url) {
error!("{}", e);
};
};
#[cfg(debug_assertions)]
return response.on_hover_text(format!("{}", url.display()));
#[cfg(not(debug_assertions))]
return response;
} else {
response
}
}
}
#[cfg(all(feature = "image_processing", feature = "gui"))]
impl ImageLink {
pub fn with_retained_image(
ctx: &egui::Context,
img: &RetainedImage,
url: Option<&Path>,
thumb_width: f32,
) -> Self {
Self {
url: url.map(|p| p.to_path_buf()),
button: ImageButton::new(
img.texture_id(ctx),
[
thumb_width,
(img.size_vec2().y / img.size_vec2().x) * thumb_width,
],
),
}
}
pub fn with_retained_image_actual_height(
ctx: &egui::Context,
img: &RetainedImage,
url: Option<&Path>,
thumb_width: f32,
) -> Self {
let ratio = img.size_vec2().y / img.size_vec2().x;
Self {
url: url.map(|p| p.to_path_buf()),
button: if let Ordering::Less = ratio.total_cmp(&MAX_TALL_IMAGE_RATIO) {
ImageButton::new(img.texture_id(ctx), [thumb_width, ratio * thumb_width])
} else {
ImageButton::new(img.texture_id(ctx), img.size_vec2())
},
}
}
}
pub struct FinderLink {
url: PathBuf,
#[cfg(feature = "gui")]
button: Button,
}
#[cfg(feature = "gui")]
impl egui::Widget for FinderLink {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
let FinderLink { url, button } = self;
let response = button.ui(ui);
if response.clicked() {
if let Err(e) = finder::reveal(&url) {
error!("{}", e);
};
};
response.on_hover_text(format!("{}", url.display()))
}
}
impl FinderLink {
pub fn with_url(_label: Option<&str>, url: &Path) -> Self {
Self {
url: url.to_path_buf(),
#[cfg(feature = "gui")]
button: Button::new(match _label {
Some(label) => label,
None => url
.file_name()
.unwrap_or(std::ffi::OsStr::new("😷 broken file name"))
.to_str()
.unwrap(),
}),
}
}
}
pub struct TurntableLink<T>
where
T: Fn(),
{
#[cfg(feature = "gui")]
button: Button,
launch_player: T,
}
#[cfg(feature = "gui")]
impl<T> egui::Widget for TurntableLink<T>
where
T: Fn(),
{
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
let TurntableLink {
button,
launch_player,
} = self;
let response = button.ui(ui);
if response.clicked() {
launch_player();
};
response.on_hover_text("Open image sequence player")
}
}
impl<T> TurntableLink<T>
where
T: Fn(),
{
pub fn with_player(_label: &str, launch_player: T) -> Self {
Self {
launch_player,
#[cfg(feature = "gui")]
button: Button::new(_label),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalLink {
#[serde(skip)]
mode: MediaMode,
name: String,
#[cfg(feature = "gui")]
icon_key: String,
url: String,
}
impl ReadWriteSuggest for ExternalLink {
fn write_suggest() -> Self {
Self::empty().with_mode(MediaMode::WriteSuggest)
}
fn with_mode(mut self, mode: MediaMode) -> Self {
self.mode_mut(mode);
self
}
fn mode(&self) -> &MediaMode {
&self.mode
}
fn mode_mut(&mut self, mode: MediaMode) {
self.mode = mode;
}
#[cfg(feature = "gui")]
fn write_compose_ui(&mut self, ui: &mut egui::Ui) {
ui.group(|ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.monospace("Name:");
ui.add(
egui::TextEdit::singleline(&mut self.name)
.hint_text("Nice name of the new external link")
.desired_width(300.),
);
});
ui.horizontal(|ui| {
ui.monospace("URL: ");
ui.add(egui::TextEdit::singleline(&mut self.url).desired_width(300.));
});
#[cfg(feature = "image_processing")]
IconCel::icon_book().select_public_icon_key_ui(&mut self.icon_key, ui);
});
});
}
}
impl ExternalLink {
pub fn empty() -> Self {
Self {
mode: MediaMode::default(),
name: String::new(),
#[cfg(feature = "gui")]
icon_key: embedded_icons::DEFAULT_LINK.to_owned(),
url: String::new(),
}
}
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
..Self::empty()
}
}
pub fn name(&self) -> &String {
&self.name
}
pub fn with_url(mut self, url: &str) -> Self {
self.url = url.to_owned();
self
}
#[cfg(feature = "gui")]
pub fn with_icon_key(mut self, key: &str) -> Self {
self.icon_key = key.to_owned();
self
}
}
#[cfg(feature = "gui")]
impl ExternalLink {
pub fn read_mode_ui_with_size(&mut self, ui: &mut egui::Ui, _size: impl Into<egui::Vec2>) {
#[cfg(feature = "image_processing")]
{
let button = egui::ImageButton::new(
IconCel::icon_book()
.get_or_default(&self.icon_key)
.texture_id(ui.ctx()),
_size,
);
if button
.ui(ui)
.on_hover_text(format!("{}: {}", self.name, self.url))
.clicked()
{
ui.ctx()
.output_mut(|o| o.open_url = Some(egui::output::OpenUrl::new_tab(&self.url)));
};
}
#[cfg(not(feature = "image_processing"))]
if ui.button(&self.name).on_hover_text(&self.url).clicked() {
ui.ctx().output_mut(|o| {
o.open_url = Some(egui::output::OpenUrl::new_tab(&self.url));
});
};
}
fn write_suggest_ui_with_size(&mut self, ui: &mut egui::Ui, _size: impl Into<egui::Vec2>) {
#[cfg(feature = "image_processing")]
{
let button = egui::ImageButton::new(
IconCel::icon_book()
.get_included(embedded_icons::ADD_LINK)
.texture_id(ui.ctx()),
_size,
);
if button
.ui(ui)
.on_hover_text("Add a new external link")
.clicked()
{
self.mode = MediaMode::WriteCompose;
};
}
#[cfg(not(feature = "image_processing"))]
{
if ui
.button("➕ New Link")
.on_hover_text("Add a new external link")
.clicked()
{
self.mode = MediaMode::WriteCompose;
};
}
}
pub fn ui(&mut self, ui: &mut egui::Ui, size: impl Into<egui::Vec2>) {
match &self.mode {
MediaMode::Read => self.read_mode_ui_with_size(ui, size),
MediaMode::WriteSuggest => {
self.write_suggest_ui_with_size(ui, size);
}
MediaMode::WriteCompose => {
self.write_compose_ui(ui);
}
MediaMode::WriteEdit => {
self.write_edit_ui(ui);
}
}
}
}
#[async_trait]
pub trait MakeWebLink: DynClone + fmt::Debug + Send + Sync {
async fn external_links(&self, project: &Project) -> Result<Vec<ExternalLink>, DatabaseError>;
async fn add_link(
&self,
project: &Project,
link: &ExternalLink,
) -> Result<(), ModificationError>;
async fn delete_link(
&self,
project: &Project,
link: &ExternalLink,
) -> Result<(), ModificationError>;
async fn edit_link(
&self,
project: &Project,
index: usize,
new: &ExternalLink,
) -> Result<(), ModificationError>;
}
dyn_clone::clone_trait_object!(MakeWebLink);
#[cfg(test)]
mod tests {
}