use anyhow::Result;
use femtovg::{FontId, Path};
use relm4::{
Sender,
gtk::gdk::{Key, ModifierType},
};
use crate::{
configuration::APP_CONFIG,
math::Vec2D,
sketch_board::{MouseButton, MouseEventMsg, MouseEventType, SketchBoardInput},
style::Style,
};
use super::{Drawable, DrawableClone, Tool, ToolUpdateResult, Tools};
#[derive(Clone, Copy, Debug)]
pub struct Rectangle {
origin: Vec2D,
top_left: Vec2D,
size: Option<Vec2D>,
style: Style,
centered: bool,
finishing: bool,
}
impl Drawable for Rectangle {
fn draw(
&self,
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
_font: FontId,
_bounds: (Vec2D, Vec2D),
) -> Result<()> {
let size = match self.size {
Some(s) => s,
None => return Ok(()), };
canvas.save();
let mut path = Path::new();
path.rounded_rect(
self.top_left.x,
self.top_left.y,
size.x,
size.y,
APP_CONFIG.read().corner_roundness(),
);
if !self.finishing && self.centered {
let mut helpers = Path::new();
helpers.circle(self.origin.x, self.origin.y, 2.0);
canvas.stroke_path(
&helpers,
&femtovg::Paint::color(femtovg::Color::rgba(128, 128, 128, 255)),
);
}
if self.style.fill {
canvas.fill_path(&path, &self.style.into());
} else {
canvas.stroke_path(&path, &self.style.into());
}
canvas.restore();
Ok(())
}
}
impl Rectangle {
fn calculate_shape(&mut self, event: &MouseEventMsg) {
self.centered = event.modifier & ModifierType::ALT_MASK == ModifierType::ALT_MASK;
match event.modifier & (ModifierType::ALT_MASK | ModifierType::SHIFT_MASK) {
v if v == ModifierType::ALT_MASK | ModifierType::SHIFT_MASK => {
let max_size = event.pos.x.abs().max(event.pos.y.abs());
self.top_left.x = self.origin.x - max_size * event.pos.x.signum() / 2.0;
self.top_left.y = self.origin.y - max_size * event.pos.y.signum() / 2.0;
self.size = Some(Vec2D {
x: max_size * event.pos.x.signum(),
y: max_size * event.pos.y.signum(),
});
}
ModifierType::ALT_MASK => {
self.top_left.x = self.origin.x - event.pos.x / 2.0;
self.top_left.y = self.origin.y - event.pos.y / 2.0;
self.size = Some(event.pos);
}
ModifierType::SHIFT_MASK => {
self.top_left = self.origin;
let max_size = event.pos.x.abs().max(event.pos.y.abs());
self.size = Some(Vec2D {
x: max_size * event.pos.x.signum(),
y: max_size * event.pos.y.signum(),
});
}
_ => {
self.top_left = self.origin;
self.size = Some(event.pos);
}
}
}
}
#[derive(Default)]
pub struct RectangleTool {
rectangle: Option<Rectangle>,
style: Style,
input_enabled: bool,
sender: Option<Sender<SketchBoardInput>>,
}
impl Tool for RectangleTool {
fn input_enabled(&self) -> bool {
self.input_enabled
}
fn set_input_enabled(&mut self, value: bool) {
self.input_enabled = value;
}
fn active(&self) -> bool {
self.rectangle.is_some()
}
fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult {
match event.type_ {
MouseEventType::BeginDrag => {
if event.button == MouseButton::Middle {
return ToolUpdateResult::Unmodified;
}
self.rectangle = Some(Rectangle {
origin: event.pos,
top_left: event.pos,
size: None,
style: self.style,
centered: false,
finishing: false,
});
ToolUpdateResult::Redraw
}
MouseEventType::EndDrag => {
if event.button == MouseButton::Middle {
return ToolUpdateResult::Unmodified;
}
if let Some(rectangle) = &mut self.rectangle {
rectangle.finishing = true;
if event.pos == Vec2D::zero() {
self.rectangle = None;
ToolUpdateResult::Redraw
} else {
rectangle.calculate_shape(&event);
let result = rectangle.clone_box();
self.rectangle = None;
ToolUpdateResult::Commit(result)
}
} else {
ToolUpdateResult::Unmodified
}
}
MouseEventType::UpdateDrag => {
if event.button == MouseButton::Middle {
return ToolUpdateResult::Unmodified;
}
if let Some(rectangle) = &mut self.rectangle {
if event.pos == Vec2D::zero() {
return ToolUpdateResult::Unmodified;
}
rectangle.calculate_shape(&event);
ToolUpdateResult::Redraw
} else {
ToolUpdateResult::Unmodified
}
}
_ => ToolUpdateResult::Unmodified,
}
}
fn handle_key_event(&mut self, event: crate::sketch_board::KeyEventMsg) -> ToolUpdateResult {
if event.key == Key::Escape && self.rectangle.is_some() {
self.rectangle = None;
ToolUpdateResult::Redraw
} else {
ToolUpdateResult::Unmodified
}
}
fn handle_style_event(&mut self, style: Style) -> ToolUpdateResult {
self.style = style;
ToolUpdateResult::Unmodified
}
fn get_drawable(&self) -> Option<&dyn Drawable> {
match &self.rectangle {
Some(d) => Some(d),
None => None,
}
}
fn get_tool_type(&self) -> super::Tools {
Tools::Rectangle
}
fn set_sender(&mut self, sender: Sender<SketchBoardInput>) {
self.sender = Some(sender);
}
}