mod annotate;
mod app;
mod capture;
mod clipboard;
mod document;
mod editor;
mod export;
mod prefs;
mod ui;
mod view;
use std::path::PathBuf;
use anyhow::{Context, Result, anyhow};
use clap::Parser;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[arg(long, value_name = "N", default_value_t = 1, value_parser = clap::value_parser!(u32).range(1..))]
screen: u32,
#[arg(long)]
pick_screen: bool,
#[arg(long)]
cursor: bool,
#[arg(long, default_value_t = 0)]
delay: u64,
#[arg(long, value_name = "DIR")]
save_path: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
from_file: Option<PathBuf>,
}
fn default_output_dir() -> PathBuf {
match std::env::home_dir() {
Some(home) if home.join("Pictures").is_dir() => home.join("Pictures/Screenshots"),
Some(home) => home.join("Screenshots"),
None => PathBuf::from("."),
}
}
fn demo_base() -> image::RgbaImage {
let (w, h) = (1600u32, 1000u32);
let mut img = image::RgbaImage::from_fn(w, h, |_, y| {
let t = y as f32 / h as f32;
let v = |a: f32, b: f32| (a + (b - a) * t) as u8;
image::Rgba([v(38.0, 24.0), v(42.0, 27.0), v(54.0, 36.0), 255])
});
let mut fill = |x0: u32, y0: u32, x1: u32, y1: u32, c: [u8; 3]| {
for y in y0..y1.min(h) {
for x in x0..x1.min(w) {
img.put_pixel(x, y, image::Rgba([c[0], c[1], c[2], 255]));
}
}
};
fill(200, 120, 1400, 880, [246, 246, 248]); fill(200, 120, 1400, 168, [228, 228, 232]); fill(222, 136, 238, 152, [224, 82, 82]); fill(248, 136, 264, 152, [240, 190, 70]);
fill(274, 136, 290, 152, [98, 197, 84]);
fill(200, 168, 470, 880, [236, 236, 240]); for i in 0..7u32 {
fill(228, 200 + i * 56, 442, 224 + i * 56, [205, 205, 212]);
}
for i in 0..5u32 {
let y = 220 + i * 64;
fill(540, y, 1330 - (i % 3) * 160, y + 26, [206, 206, 214]);
}
let colors = [[210u8, 90, 90], [90, 140, 210], [120, 180, 95], [205, 160, 80]];
for i in 0..24u32 {
let x = 545 + i * 19;
fill(x, 414 + (i % 3) * 5, x + 13, 452 - (i % 2) * 7, colors[(i % 4) as usize]);
}
fill(540, 560, 1330, 700, [222, 228, 238]); fill(1130, 760, 1350, 830, [47, 82, 224]); fill(1190, 786, 1290, 804, [235, 240, 252]); img
}
fn main() -> Result<()> {
let cli = Cli::parse();
let demo_mode = std::env::var("SCRANNOTATE_DEMO").ok();
let demo = demo_mode.is_some();
let img = match &cli.from_file {
Some(path) => image::open(path)
.with_context(|| format!("opening {}", path.display()))?
.to_rgba8(),
None if demo => demo_base(),
None => {
if cli.delay > 0 {
std::thread::sleep(std::time::Duration::from_secs(cli.delay));
}
capture::capture(&capture::CaptureOptions {
cursor: cli.cursor,
pick_screen: cli.pick_screen,
screen: cli.screen,
})?
}
};
let out_dir = cli.save_path.unwrap_or_else(default_output_dir);
let select_full = cli.from_file.is_some();
let mut viewport = eframe::egui::ViewportBuilder::default()
.with_app_id("scrannotate")
.with_title("scrannotate");
viewport = if demo {
viewport.with_inner_size([1680.0, 1160.0])
} else {
viewport.with_fullscreen(true)
};
let options = eframe::NativeOptions { viewport, ..Default::default() };
eframe::run_native(
"scrannotate",
options,
Box::new(move |_cc| {
Ok(Box::new(app::ScreencapApp::new(img, out_dir, select_full, demo_mode)))
}),
)
.map_err(|err| anyhow!("running ui: {err}"))
}