use egui::Pos2;
use image::{ImageBuffer, Rgba};
use super::composite::composite_annotations;
use super::stroke::{Annotations, StrokeSettings};
pub struct ActionContext<'a> {
pub selection: Option<((u32, u32), (u32, u32))>,
pub selection_logical: Option<((f32, f32), (f32, f32))>,
pub screenshot: Option<&'a ImageBuffer<Rgba<u8>, Vec<u8>>>,
pub annotations: Option<&'a Annotations>,
pub monitor: Option<&'a xcap::Monitor>,
pub scale_factor: f64,
}
impl<'a> ActionContext<'a> {
pub fn new(
selection: Option<((u32, u32), (u32, u32))>,
selection_logical: Option<((f32, f32), (f32, f32))>,
screenshot: Option<&'a ImageBuffer<Rgba<u8>, Vec<u8>>>,
annotations: Option<&'a Annotations>,
monitor: Option<&'a xcap::Monitor>,
scale_factor: f64,
) -> Self {
Self { selection, selection_logical, screenshot, annotations, monitor, scale_factor }
}
fn get_selected_region(&self) -> Option<ImageBuffer<Rgba<u8>, Vec<u8>>> {
let ((x1, y1), (x2, y2)) = self.selection?;
let screenshot = self.screenshot?;
let width = x2.saturating_sub(x1);
let height = y2.saturating_sub(y1);
if width == 0 || height == 0 {
return None;
}
let mut region = ImageBuffer::new(width, height);
for y in 0..height {
for x in 0..width {
let src_x = x1 + x;
let src_y = y1 + y;
if src_x < screenshot.width() && src_y < screenshot.height() {
let pixel = screenshot.get_pixel(src_x, src_y);
region.put_pixel(x, y, *pixel);
}
}
}
Some(region)
}
pub fn get_composited_region(&self) -> Option<ImageBuffer<Rgba<u8>, Vec<u8>>> {
let selection = self.selection?;
let selection_logical = self.selection_logical?;
let screenshot = self.screenshot?;
let annotations = match self.annotations {
Some(a) if !a.is_empty() => a,
_ => return self.get_selected_region(),
};
Some(composite_annotations(
screenshot,
annotations,
selection,
selection_logical,
self.scale_factor as f32,
))
}
}
#[derive(Debug, Clone)]
pub enum ActionResult {
Success,
Failure(String),
Exit,
Continue,
Undo,
Redo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ToolCategory {
Drawing,
Privacy,
#[default]
Action,
}
#[derive(Debug, Clone)]
pub struct ActionInfo {
pub id: String,
pub name: String,
pub icon_id: Option<String>,
pub category: ToolCategory,
}
pub struct RenderContext<'a> {
pub ui: &'a egui::Ui,
pub annotations: &'a Annotations,
pub scale_factor: f32,
}
impl<'a> RenderContext<'a> {
pub fn new(ui: &'a egui::Ui, annotations: &'a Annotations, scale_factor: f32) -> Self {
Self { ui, annotations, scale_factor }
}
}
pub struct DrawingContext<'a> {
pub annotations: &'a mut Annotations,
pub settings: &'a StrokeSettings,
pub selection_bounds: Option<((f32, f32), (f32, f32))>,
pub scale_factor: f32,
}
impl<'a> DrawingContext<'a> {
pub fn new(
annotations: &'a mut Annotations,
settings: &'a StrokeSettings,
selection_bounds: Option<((f32, f32), (f32, f32))>,
scale_factor: f32,
) -> Self {
Self { annotations, settings, selection_bounds, scale_factor }
}
pub fn is_in_bounds(&self, pos: Pos2) -> bool {
match self.selection_bounds {
Some(((min_x, min_y), (max_x, max_y))) => {
pos.x >= min_x && pos.x <= max_x && pos.y >= min_y && pos.y <= max_y
}
None => true, }
}
pub fn clamp_to_bounds(&self, pos: Pos2) -> Pos2 {
match self.selection_bounds {
Some(((min_x, min_y), (max_x, max_y))) => {
Pos2::new(pos.x.clamp(min_x, max_x), pos.y.clamp(min_y, max_y))
}
None => pos,
}
}
}
pub trait ScreenAction: Send + Sync {
fn id(&self) -> &str;
fn name(&self) -> &str;
fn icon_id(&self) -> Option<&str> {
None
}
fn on_click(&mut self, ctx: &ActionContext) -> ActionResult;
fn category(&self) -> ToolCategory {
ToolCategory::Action
}
fn is_active(&self) -> bool {
false
}
fn set_active(&mut self, _active: bool) {}
fn is_drawing_tool(&self) -> bool {
false
}
fn on_draw_start(&mut self, _pos: Pos2, _ctx: &mut DrawingContext) {}
fn on_draw_move(&mut self, _pos: Pos2, _ctx: &mut DrawingContext) {}
fn on_draw_end(&mut self, _ctx: &mut DrawingContext) {}
fn render_preview(&self, _ui: &egui::Ui) {}
fn render_annotations(&self, _ctx: &RenderContext) {}
fn info(&self) -> ActionInfo {
ActionInfo {
id: self.id().to_string(),
name: self.name().to_string(),
icon_id: self.icon_id().map(|s| s.to_string()),
category: self.category(),
}
}
}