use bladvak::eframe::egui::{
self, Color32, ColorImage, Image, ImageSource, TextFormat, include_image, text::LayoutJob,
};
use bladvak::errors::ErrorManager;
use image::ImageFormat;
use std::path::Path;
use std::{io::Cursor, path::PathBuf};
use crate::{TarsierApp, side_panel::EditMode};
impl TarsierApp {
const RESET_ICON: ImageSource<'_> = include_image!("../assets/icon_x-circle.png");
const SAVE_STATE_ICON: ImageSource<'_> = include_image!("../assets/icon_check.png");
const ROTATE_CCW_ICON: ImageSource<'_> = include_image!("../assets/icon_rotate_ccw.png");
const ROTATE_CW_ICON: ImageSource<'_> = include_image!("../assets/icon_rotate_cw.png");
const FLIP_H_ICON: ImageSource<'_> = include_image!("../assets/icon_flip_h.png");
const FLIP_V_ICON: ImageSource<'_> = include_image!("../assets/icon_flip_v.png");
pub(crate) fn app_menu_file(&mut self, ui: &mut egui::Ui, error_manager: &mut ErrorManager) {
if ui.button("New").clicked() {
self.new_image.is_open = true;
ui.close();
}
self.menu_clipboard(ui, error_manager);
ui.menu_button("Save", |ui| {
if ui.button("PNG").clicked() {
ui.close();
self.save_image_with_format(
ImageFormat::Png.extensions_str()[0],
ImageFormat::Png,
error_manager,
);
}
if ui.button("JPEG").clicked() {
ui.close();
self.save_image_with_format(
ImageFormat::Jpeg.extensions_str()[0],
ImageFormat::Jpeg,
error_manager,
);
}
if ui.button("BMP").clicked() {
ui.close();
self.save_image_with_format(
ImageFormat::Bmp.extensions_str()[0],
ImageFormat::Bmp,
error_manager,
);
}
if ui.button("GIF").clicked() {
ui.close();
self.save_image_with_format(
ImageFormat::Gif.extensions_str()[0],
ImageFormat::Gif,
error_manager,
);
}
});
}
pub fn menu_clipboard(&mut self, ui: &mut egui::Ui, error_manager: &mut ErrorManager) {
ui.menu_button("Clipboard", |ui| {
if ui.button("Copy").clicked()
&& let Err(e) = bladvak::utils::set_image_in_clipboard(
ui.ctx(),
self.img.width() as usize,
self.img.height() as usize,
self.img.to_rgba8().as_flat_samples().as_slice(),
)
{
error_manager.add_error(e);
}
#[cfg(not(target_arch = "wasm32"))]
if ui.button("Paste").clicked() {
match bladvak::utils::get_image_from_clipboard() {
Ok((width, height, rgba_data)) => {
#[allow(clippy::cast_possible_truncation)]
if let Some(buffer) = image::ImageBuffer::from_raw(
width as u32,
height as u32,
rgba_data.clone(),
) {
let img = image::DynamicImage::ImageRgba8(buffer);
self.update_image(img);
} else {
error_manager
.add_error("Invalid image data from clipboard".to_string());
}
}
Err(e) => {
error_manager.add_error(e);
}
}
}
});
}
pub(crate) fn save_image_with_format(
&mut self,
extension: &str,
image_format: ImageFormat,
error_manager: &mut ErrorManager,
) {
let current_save_path = self
.save_path
.clone()
.unwrap_or(PathBuf::from(format!("image.{extension}")));
let save_path = bladvak::utils::get_save_path(Some(¤t_save_path));
match save_path {
Ok(save_p) => {
self.save_path.clone_from(&save_p);
if let Some(path_to_save) = save_p
&& let Err(err) = self.save_image(image_format, &path_to_save)
{
error_manager.add_error(err);
}
}
Err(e) => {
error_manager.add_error(e);
}
}
}
fn top_panel_operations(
&mut self,
ui: &mut egui::Ui,
error_manager: &mut ErrorManager,
is_dark_theme: bool,
) {
let ico_image = Image::new(Self::RESET_ICON);
if ui
.add(egui::Button::image(if is_dark_theme {
ico_image
} else {
ico_image.tint(Color32::BLACK)
}))
.on_hover_text("Reset the image to the saved state")
.clicked()
{
let new_img = self.saved_img.clone();
self.update_image(new_img);
}
let ico_image = Image::new(Self::SAVE_STATE_ICON);
if ui
.add(egui::Button::image(if is_dark_theme {
ico_image
} else {
ico_image.tint(Color32::BLACK)
}))
.on_hover_text("Save the current image state")
.clicked()
{
self.saved_img = self.img.clone();
}
ui.separator();
let ico_image = Image::new(Self::ROTATE_CW_ICON);
if ui
.add(egui::Button::image(if is_dark_theme {
ico_image
} else {
ico_image.tint(Color32::BLACK)
}))
.on_hover_text("Rotate 90 degrees clockwise")
.clicked()
{
self.apply_op(image::DynamicImage::rotate90, error_manager);
}
let ico_image = Image::new(Self::ROTATE_CCW_ICON);
if ui
.add(egui::Button::image(if is_dark_theme {
ico_image
} else {
ico_image.tint(Color32::BLACK)
}))
.on_hover_text("Rotate 90 degrees counter-clockwise")
.clicked()
{
self.apply_op(image::DynamicImage::rotate270, error_manager);
}
let ico_image = Image::new(Self::FLIP_H_ICON);
if ui
.add(egui::Button::image(if is_dark_theme {
ico_image
} else {
ico_image.tint(Color32::BLACK)
}))
.on_hover_text("Flip horizontally")
.clicked()
{
self.apply_op(image::DynamicImage::fliph, error_manager);
}
let ico_image = Image::new(Self::FLIP_V_ICON);
if ui
.add(egui::Button::image(if is_dark_theme {
ico_image
} else {
ico_image.tint(Color32::BLACK)
}))
.on_hover_text("Flip vertically")
.clicked()
{
self.apply_op(image::DynamicImage::flipv, error_manager);
}
}
pub(crate) fn app_top_panel(&mut self, ui: &mut egui::Ui, error_manager: &mut ErrorManager) {
let is_dark_theme = ui.ctx().global_style().visuals.dark_mode;
ui.separator();
self.top_panel_operations(ui, error_manager, is_dark_theme);
ui.separator();
let (default_color, background_color) = if ui.visuals().dark_mode {
(Color32::LIGHT_GRAY, Color32::DARK_BLUE)
} else {
(Color32::DARK_GRAY, Color32::LIGHT_BLUE)
};
let mut job = LayoutJob::default();
job.append(
"Cursor ",
0.0,
TextFormat {
color: default_color,
..Default::default()
},
);
job.append(
&format!("{}", self.image_operations.mode),
0.0,
TextFormat {
color: default_color,
background: background_color,
..Default::default()
},
);
ui.menu_button(job, |ui| {
let previous_state = self.image_operations.mode;
ui.selectable_value(
&mut self.image_operations.mode,
EditMode::Selection,
"Selection",
);
ui.selectable_value(
&mut self.image_operations.mode,
EditMode::Drawing,
"Drawing",
);
if self.image_operations.mode != previous_state {
ui.close();
if self.image_operations.mode == EditMode::Drawing {
self.cursor_info.selection = None;
}
}
});
if let Some(selection) = self.cursor_info.selection {
ui.separator();
if ui
.label(format!(
"Selection: {:.0}x{:.0}",
(selection.max.x - selection.min.x).abs(),
(selection.max.y - selection.min.y).abs()
))
.on_hover_text("Click to clear selection")
.clicked()
{
self.cursor_info.selection = None;
}
}
ui.separator();
if ui.button("Copy").clicked() {
let size = [self.img.width() as _, self.img.height() as _];
let rgb_img = self.img.to_rgba8();
let pixels = rgb_img.as_flat_samples();
let color_image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
ui.ctx().copy_image(color_image);
}
}
}
impl TarsierApp {
fn save_image(&mut self, format: ImageFormat, path_file: &Path) -> Result<(), String> {
let mut bytes: Vec<u8> = Vec::new();
self.img
.write_to(&mut Cursor::new(&mut bytes), format)
.map_err(|e| format!("Cannot write image: {e}"))?;
bladvak::utils::save_file(&bytes, path_file)
}
}