use bladvak::eframe::egui::{
self, Color32, ColorImage, Id, Image, ImageData, Modal, Pos2, Sense, Stroke, TextureOptions,
Vec2,
};
use bladvak::errors::ErrorManager;
use image::DynamicImage;
use std::path::PathBuf;
use std::sync::Arc;
use crate::{TarsierApp, edit_mode::EditMode};
impl TarsierApp {
#[allow(clippy::too_many_lines)]
#[allow(clippy::cast_precision_loss)]
pub(crate) fn app_central_panel(
&mut self,
ui: &mut egui::Ui,
_error_manager: &mut ErrorManager,
) {
let Some(document) = self.documents.get_current_doc_mut() else {
egui::Area::new("center".into())
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
.show(ui.ctx(), |ui| {
ui.vertical_centered(|ui| {
ui.heading(concat!("Welcome to ", env!("CARGO_PKG_NAME")));
ui.label("No document opened");
});
});
return;
};
let mut rect = document.scene_rect;
egui::Scene::new().show(ui, &mut rect, |ui| {
let Some(document) = self.documents.get_current_doc_mut() else {
return;
};
let painter = ui.painter();
let bg_r: egui::Response = ui.response();
if bg_r.rect.is_finite() {
self.grid.draw(&bg_r.rect, painter);
}
let size = [document.img.width() as _, document.img.height() as _];
let image_texture = document.texture.get_or_insert_with(|| {
let image_buffer = document.img.to_rgba8();
let pixels = image_buffer.as_flat_samples();
let image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
ui.ctx().load_texture(
"img",
ImageData::Color(Arc::new(image)),
TextureOptions::default(),
)
});
let response = ui.add(
Image::new((image_texture.id(), image_texture.size_vec2()))
.sense(Sense::click_and_drag()),
);
let is_dark_theme = ui.ctx().global_style().visuals.dark_mode;
ui.painter().rect_stroke(
response.rect,
0.0, Stroke::new(
1.0,
if is_dark_theme {
Color32::WHITE
} else {
Color32::BLACK
},
),
egui::StrokeKind::Outside,
);
let img_position = response.rect;
let viewport = ui.ctx().viewport_rect();
let ecart_x = img_position.min.x + viewport.min.x;
let ecart_y = img_position.min.y + viewport.min.y;
let painter = ui.painter();
if let EditMode::Drawing = self.mode.current
&& let Some(pos) = ui.ctx().input(|i| i.pointer.clone()).latest_pos()
{
painter.circle(
pos,
self.mode.drawing.pen_radius as f32,
Color32::TRANSPARENT,
egui::Stroke::new(1.0, Color32::BLACK),
);
}
if response.dragged() {
if let Some(pos) = response.interact_pointer_pos() {
if !document.selection.is_selecting {
document.selection.rectangle = None;
}
let pos = pos - Vec2::new(ecart_x - viewport.min.x, ecart_y - viewport.min.y);
let correct_pos = Pos2::new(
pos.x.round().clamp(0.0, size[0] as f32),
pos.y.round().clamp(0.0, size[1] as f32),
);
match self.mode.current {
EditMode::Cursor => {
}
EditMode::Selection => {
document.selection.rectangle =
if let Some(_rect) = document.selection.rectangle {
Some(egui::Rect::from_two_pos(
document.selection.start_selection,
correct_pos,
))
} else {
document.selection.start_selection = correct_pos;
Some(egui::Rect::from_two_pos(correct_pos, correct_pos))
};
document.selection.is_selecting = true;
}
EditMode::Drawing => {
#[allow(clippy::cast_possible_truncation)]
if self.mode.drawing.drawing_continuous_line
&& let Some(last_post) = document.selection.last_drawing_point
{
let pts = points_between(last_post, correct_pos);
for p in pts {
self.draw_point(p.x as i32, p.y as i32);
}
} else {
self.draw_point(correct_pos.x as i32, correct_pos.y as i32);
}
if let Some(document) = self.documents.get_current_doc_mut() {
document.selection.last_drawing_point = Some(correct_pos);
}
}
}
}
} else {
document.selection.is_selecting = false;
document.selection.last_drawing_point = None;
}
let Some(document) = self.documents.get_current_doc_mut() else {
return;
};
if response.clicked() {
document.selection.rectangle = None;
match self.mode.current {
EditMode::Cursor => {
}
EditMode::Selection => {
document.selection.rectangle = None;
}
EditMode::Drawing => {
if let Some(pos) = response.interact_pointer_pos() {
let pos =
pos - Vec2::new(ecart_x - viewport.min.x, ecart_y - viewport.min.y);
let correct_pos = Pos2::new(
pos.x.round().clamp(0.0, size[0] as f32),
pos.y.round().clamp(0.0, size[1] as f32),
);
#[allow(clippy::cast_possible_truncation)]
self.draw_point(correct_pos.x as i32, correct_pos.y as i32);
if let Some(document) = self.documents.get_current_doc_mut() {
document.selection.last_drawing_point = None;
}
}
}
}
}
let Some(document) = self.documents.get_current_doc_mut() else {
return;
};
if let Some(selection) = document.selection.rectangle {
let min_pos = img_position.min;
let rect_selection = egui::Rect::from_two_pos(
Pos2::new(
selection.min.x - viewport.min.x + ecart_x,
selection.min.y - viewport.min.y + ecart_y,
),
Pos2::new(
selection.max.x - viewport.min.x + ecart_x,
selection.max.y - viewport.min.y + ecart_y,
),
);
painter.rect_stroke(
rect_selection,
0.0,
egui::Stroke::new(1.0, Color32::BLACK),
egui::StrokeKind::Outside,
);
painter.rect_filled(
egui::Rect::from_min_max(
Pos2::new(min_pos.x, min_pos.y),
Pos2::new(size[0] as f32 + min_pos.x, rect_selection.min.y),
),
0.0,
Color32::from_black_alpha(50),
);
painter.rect_filled(
egui::Rect::from_min_max(
Pos2::new(min_pos.x, rect_selection.max.y),
Pos2::new(size[0] as f32 + min_pos.x, size[1] as f32 + min_pos.y),
),
0.0,
Color32::from_black_alpha(50),
);
painter.rect_filled(
egui::Rect::from_min_max(
Pos2::new(min_pos.x, rect_selection.min.y),
Pos2::new(rect_selection.min.x, rect_selection.max.y),
),
0.0,
Color32::from_black_alpha(50),
);
painter.rect_filled(
egui::Rect::from_min_max(
Pos2::new(rect_selection.max.x, rect_selection.min.y),
Pos2::new(size[0] as f32 + min_pos.x, rect_selection.max.y),
),
0.0,
Color32::from_black_alpha(50),
);
}
});
if let Some(document) = self.documents.get_current_doc_mut() {
document.scene_rect = rect;
}
}
pub(crate) fn show_new_image_modal(&mut self, ui: &mut egui::Ui) {
if self.settings.new_image.is_open {
let modal = Modal::new(Id::new("Modal new image")).show(ui.ctx(), |ui| {
ui.label("Create a new image");
ui.horizontal(|ui| {
ui.label("Width");
ui.add(
egui::DragValue::new(&mut self.settings.new_image.width).range(1..=4000),
);
});
ui.horizontal(|ui| {
ui.label("Height");
ui.add(
egui::DragValue::new(&mut self.settings.new_image.height).range(1..=4000),
);
});
ui.label("Color type");
Self::combo_box_color_type(ui, &mut self.settings.new_image.color_type);
egui::Sides::new().show(
ui,
|modal_ui| {
if modal_ui.button("Cancel").clicked() {
modal_ui.close();
}
},
|modal_ui| {
if modal_ui.button("Create").clicked() {
let new_img = DynamicImage::new(
self.settings.new_image.width,
self.settings.new_image.height,
self.settings.new_image.color_type,
);
self.new_file(PathBuf::from("new.png"), new_img, None);
modal_ui.close();
}
},
);
});
if modal.should_close() {
self.settings.new_image.is_open = false;
}
}
}
}
fn points_between(a: Pos2, b: Pos2) -> Vec<Pos2> {
let mut points = Vec::new();
let (mut x1, mut y1) = (a.x, a.y);
let (x2, y2) = (b.x, b.y);
let dx = (x2 - x1).abs();
let dy = -(y2 - y1).abs();
let sx = if x1 < x2 { 1.0 } else { -1.0 };
let sy = if y1 < y2 { 1.0 } else { -1.0 };
let mut err = dx + dy;
let error_margin = 0.01;
loop {
points.push(Pos2 { x: x1, y: y1 });
if (x1 - x2).abs() < error_margin && (y1 - y2).abs() < error_margin {
break;
}
let e2 = 2.0 * err;
if e2 >= dy {
err += dy;
x1 += sx;
}
if e2 <= dx {
err += dx;
y1 += sy;
}
}
points
}