use std::{
borrow::Cow,
cell::RefCell,
collections::HashMap,
fmt::{Debug, Display},
rc::Rc,
};
use anyhow::Result;
use femtovg::{Canvas, FontId, renderer::OpenGl};
use relm4::gtk::gdk_pixbuf::{
glib::{Variant, VariantTy},
prelude::{StaticVariantType, ToVariant},
};
use relm4::gtk::glib::variant::FromVariant;
use relm4::{
Sender,
gtk::{self, IMMulticontext},
};
use serde_derive::Deserialize;
use crate::{
math::Vec2D,
sketch_board::{InputEvent, KeyEventMsg, MouseEventMsg, SketchBoardInput, TextEventMsg},
style::Style,
};
use satty_cli::command_line;
mod arrow;
mod blur;
mod brush;
mod crop;
mod ellipse;
mod highlight;
mod line;
mod marker;
mod pointer;
mod rectangle;
mod text;
pub enum ToolEvent {
Activated,
Deactivated,
Input(InputEvent),
StyleChanged(Style),
}
pub trait Tool {
fn handle_event(&mut self, event: ToolEvent) -> ToolUpdateResult {
match event {
ToolEvent::Activated => self.handle_activated(),
ToolEvent::Deactivated => self.handle_deactivated(),
ToolEvent::Input(e) => self.handle_input_event(e),
ToolEvent::StyleChanged(s) => self.handle_style_event(s),
}
}
fn handle_activated(&mut self) -> ToolUpdateResult {
ToolUpdateResult::Unmodified
}
fn handle_deactivated(&mut self) -> ToolUpdateResult {
ToolUpdateResult::Unmodified
}
fn handle_input_event(&mut self, event: InputEvent) -> ToolUpdateResult {
match event {
InputEvent::Mouse(e) => self.handle_mouse_event(e),
InputEvent::Key(e) => self.handle_key_event(e),
InputEvent::KeyRelease(e) => self.handle_key_release_event(e),
InputEvent::Text(e) => self.handle_text_event(e),
}
}
fn handle_text_event(&mut self, event: TextEventMsg) -> ToolUpdateResult {
let _ = event;
ToolUpdateResult::Unmodified
}
fn handle_mouse_event(&mut self, event: MouseEventMsg) -> ToolUpdateResult {
let _ = event;
ToolUpdateResult::Unmodified
}
fn handle_key_event(&mut self, event: KeyEventMsg) -> ToolUpdateResult {
let _ = event;
ToolUpdateResult::Unmodified
}
fn handle_key_release_event(&mut self, event: KeyEventMsg) -> ToolUpdateResult {
let _ = event;
ToolUpdateResult::Unmodified
}
fn handle_style_event(&mut self, style: Style) -> ToolUpdateResult {
let _ = style;
ToolUpdateResult::Unmodified
}
fn active(&self) -> bool {
false
}
fn input_enabled(&self) -> bool;
fn set_input_enabled(&mut self, value: bool);
fn handle_undo(&mut self) -> ToolUpdateResult {
ToolUpdateResult::Unmodified
}
fn handle_redo(&mut self) -> ToolUpdateResult {
ToolUpdateResult::Unmodified
}
fn set_im_context(&mut self, _context: Option<InputContext>) {}
fn get_drawable(&self) -> Option<&dyn Drawable>;
fn get_tool_type(&self) -> Tools;
fn set_sender(&mut self, sender: Sender<SketchBoardInput>);
}
#[derive(Clone, Debug)]
pub struct InputContext {
pub im_context: IMMulticontext,
pub widget: gtk::Widget,
}
pub trait DrawableClone {
fn clone_box(&self) -> Box<dyn Drawable>;
}
impl<T> DrawableClone for T
where
T: 'static + Drawable + Clone,
{
fn clone_box(&self) -> Box<dyn Drawable> {
Box::new(self.clone())
}
}
pub trait Drawable: DrawableClone + Debug {
fn draw(&self, canvas: &mut Canvas<OpenGl>, font: FontId, bounds: (Vec2D, Vec2D))
-> Result<()>;
fn handle_undo(&mut self) {}
fn handle_redo(&mut self) {}
}
#[derive(Debug)]
pub enum ToolUpdateResult {
Commit(Box<dyn Drawable>),
Redraw,
Unmodified,
StopPropagation,
RedrawAndStopPropagation,
}
pub use arrow::ArrowTool;
pub use blur::BlurTool;
pub use crop::CropTool;
pub use ellipse::EllipseTool;
pub use highlight::{HighlightTool, Highlighters};
pub use line::LineTool;
pub use rectangle::RectangleTool;
pub use text::TextTool;
use self::{brush::BrushTool, marker::MarkerTool, pointer::PointerTool};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Tools {
Pointer = 0,
Crop = 1,
Line = 2,
Arrow = 3,
Rectangle = 4,
Ellipse = 5,
Text = 6,
Marker = 7,
Blur = 8,
Highlight = 9,
Brush = 10,
}
impl Tools {
pub fn display_name(&self) -> &'static str {
match self {
Tools::Pointer => "Pointer",
Tools::Crop => "Crop",
Tools::Brush => "Brush",
Tools::Line => "Line",
Tools::Arrow => "Arrow",
Tools::Rectangle => "Rectangle",
Tools::Ellipse => "Ellipse",
Tools::Text => "Text",
Tools::Marker => "Numbered Marker",
Tools::Blur => "Blur",
Tools::Highlight => "Highlight",
}
}
}
impl Display for Tools {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pointer => write!(f, "pointer"),
Self::Crop => write!(f, "crop"),
Self::Line => write!(f, "line"),
Self::Arrow => write!(f, "arrow"),
Self::Rectangle => write!(f, "rectangle"),
Self::Ellipse => write!(f, "ellipse"),
Self::Text => write!(f, "text"),
Self::Marker => write!(f, "marker"),
Self::Blur => write!(f, "blur"),
Self::Highlight => write!(f, "highlight"),
Self::Brush => write!(f, "brush"),
}
}
}
pub struct ToolsManager {
tools: HashMap<Tools, Rc<RefCell<dyn Tool>>>,
crop_tool: Rc<RefCell<CropTool>>,
}
impl ToolsManager {
pub fn new() -> Self {
let mut tools: HashMap<Tools, Rc<RefCell<dyn Tool>>> = HashMap::new();
tools.insert(
Tools::Pointer,
Rc::new(RefCell::new(PointerTool::default())),
);
tools.insert(Tools::Line, Rc::new(RefCell::new(LineTool::default())));
tools.insert(Tools::Arrow, Rc::new(RefCell::new(ArrowTool::default())));
tools.insert(
Tools::Rectangle,
Rc::new(RefCell::new(RectangleTool::default())),
);
tools.insert(
Tools::Ellipse,
Rc::new(RefCell::new(EllipseTool::default())),
);
tools.insert(Tools::Text, Rc::new(RefCell::new(TextTool::default())));
tools.insert(Tools::Blur, Rc::new(RefCell::new(BlurTool::default())));
tools.insert(
Tools::Highlight,
Rc::new(RefCell::new(HighlightTool::default())),
);
tools.insert(Tools::Marker, Rc::new(RefCell::new(MarkerTool::default())));
tools.insert(Tools::Brush, Rc::new(RefCell::new(BrushTool::default())));
let crop_tool = Rc::new(RefCell::new(CropTool::default()));
Self { tools, crop_tool }
}
pub fn get(&self, tool: &Tools) -> Rc<RefCell<dyn Tool>> {
match tool {
Tools::Crop => self.crop_tool.clone(),
_ => self
.tools
.get(tool)
.unwrap_or_else(|| {
panic!("Did you add the requested too {tool:#?} to the tools HashMap?")
})
.clone(),
}
}
pub fn get_crop_tool(&self) -> Rc<RefCell<CropTool>> {
self.crop_tool.clone()
}
}
impl StaticVariantType for Tools {
fn static_variant_type() -> Cow<'static, VariantTy> {
Cow::Borrowed(VariantTy::UINT32)
}
}
impl ToVariant for Tools {
fn to_variant(&self) -> Variant {
Variant::from(*self as u32)
}
}
impl FromVariant for Tools {
fn from_variant(variant: &Variant) -> Option<Self> {
variant.get::<u32>().and_then(|v| match v {
0 => Some(Tools::Pointer),
1 => Some(Tools::Crop),
2 => Some(Tools::Line),
3 => Some(Tools::Arrow),
4 => Some(Tools::Rectangle),
5 => Some(Tools::Ellipse),
6 => Some(Tools::Text),
7 => Some(Tools::Marker),
8 => Some(Tools::Blur),
9 => Some(Tools::Highlight),
10 => Some(Tools::Brush),
_ => None,
})
}
}
impl From<command_line::Tools> for Tools {
fn from(tool: command_line::Tools) -> Self {
match tool {
command_line::Tools::Pointer => Self::Pointer,
command_line::Tools::Crop => Self::Crop,
command_line::Tools::Line => Self::Line,
command_line::Tools::Arrow => Self::Arrow,
command_line::Tools::Rectangle => Self::Rectangle,
command_line::Tools::Ellipse => Self::Ellipse,
command_line::Tools::Text => Self::Text,
command_line::Tools::Marker => Self::Marker,
command_line::Tools::Blur => Self::Blur,
command_line::Tools::Highlight => Self::Highlight,
command_line::Tools::Brush => Self::Brush,
}
}
}