Skip to content

rferrari/rabisco

Repository files navigation

Rabisco

A lightweight on-screen annotation tool in Rust.

Draw, highlight, and annotate on your screen during presentations, video calls, tutorials, all without interrupting your workflow.

Features

  • Transparent fullscreen overlay — draws on top of everything, invisible when not in use
  • Passthrough mode — click-through to the desktop when you're not drawing; toggle with Space or the toolbar button
  • Tools
    • Pen — freehand drawing with configurable width (1 / 3 / 8 px)
    • Highlighter — semi-transparent strokes (12 / 24 / 40 px)
    • Eraser — removes strokes by proximity
    • Shapes — Arrow (default), Line, Rectangle, Circle; drag to draw; click to cycle shape
    • Text — click to place, type, Enter to commit
  • 24-color palette — pick from the color popup
  • Fading ink — strokes fade out automatically (1 / 2 / 3 / 5 s); on by default at 2 s
  • Cursor spotlight — tool icon drawn at the pointer position, great for screen sharing
  • Backgrounds — Transparent / White (50% opacity) / Black (50% opacity)
  • Undo (Ctrl+Z) and Clear all (Delete)
  • Persistent settings — tool, color, widths, and toolbar position are saved automatically

Installation

Pre-built binary (Linux x86_64)

Download the latest rabisco binary and its checksum from the Releases page.

Verify and run:

sha256sum -c rabisco.sha256
chmod +x rabisco
./rabisco

No runtime dependencies beyond a display server (X11 or Wayland) and a Vulkan-capable GPU.

Requirements

  • Linux (X11 or Wayland)
  • A GPU with Vulkan support (via wgpu)

Build & Run

cargo build --release
./target/release/rabisco

Requires a running display server. Debug builds work fine for development:

cargo run

Keyboard Shortcuts

Key Action
Space Toggle draw / passthrough mode
1–5 Switch tool (Pen / Highlighter / Eraser / Shapes / Text)
Enter Commit text (Text tool)
Ctrl+Z Undo last stroke
Delete / Backspace Clear all strokes
Escape Switch to passthrough mode

Toolbar

The floating toolbar lives in its own OS window so it stays clickable even when the canvas is in passthrough mode.

  • Click a tool button to select it
  • Hold a tool button (0.4 s) to open its options popup (width presets, shape picker, etc.)
  • Shapes tool: click again while selected to cycle Arrow → Line → Rect → Circle
  • Drag the grip dots at the top to reposition the toolbar anywhere on screen
  • Collapse with the button; the pill shows the active drawing color
  • Single-click the pill to toggle draw/passthrough mode
  • Double-click the pill to expand the toolbar again

Settings

Settings are saved to ~/.config/rabisco/settings.json automatically whenever you switch tools or change any style option.


Developer Guide

Project Structure

src/
  main.rs          — eframe entry point, window configuration
  app.rs           — RabiscoApp: main loop, keyboard shortcuts, command dispatch
  canvas.rs        — Canvas, DrawnStroke, StrokeKind; render, erase, fading
  config.rs        — DrawStyle (Copy), ShapeKind, Background, PALETTE
  settings.rs      — Settings struct, JSON load/save (~/.config/rabisco/)
  tools/
    mod.rs         — Tool trait + CanvasCommand enum
    pen.rs
    highlighter.rs
    eraser.rs
    shapes.rs      — ShapesTool, shape_icon(), cycle logic
    text_tool.rs   — click-to-place, direct keyboard capture
  ui/
    toolbar.rs     — floating toolbar viewport, hold-to-open popups
    mod.rs

Architecture Overview

Two-window model. The app runs two OS windows: a fullscreen transparent canvas and a small toolbar. The toolbar is a secondary egui viewport (show_viewport_immediate), which requires the wgpu renderer — the glow backend does not support multiple viewports. The main canvas uses ViewportCommand::MousePassthrough to become click-through; the toolbar is never passthrough, so it stays interactive regardless of draw mode.

Command pattern. Tools never hold a reference to Canvas. Tool::handle_input() returns Vec<CanvasCommand>, which RabiscoApp::apply_commands() processes after the borrow of self.tools[i] is released. This avoids borrow-checker conflicts. DrawStyle is Copy for the same reason — it is passed by value so &mut self.tools[i] and self.style can coexist.

Settings change detection. app.rs snapshots style, active_tool, and background before the toolbar viewport call each frame. If anything changed afterwards, save_settings() writes the JSON immediately. No timer or dirty flag is needed.

Creating a New Tool

  1. Create src/tools/my_tool.rs and implement the Tool trait:
use egui::{Painter, Response};
use crate::config::DrawStyle;
use super::{CanvasCommand, Tool};

pub struct MyTool;

impl Tool for MyTool {
    fn name(&self) -> &'static str { "My Tool" }

    // Use ASCII or safe Unicode only — emoji may not render in egui's fonts
    fn icon(&self) -> &'static str { "M" }

    fn handle_input(
        &mut self,
        response: &Response,
        painter: &Painter,
        style: DrawStyle,
        time: f64,
    ) -> Vec<CanvasCommand> {
        vec![]
    }
}
  1. Register it in app.rs:
let tools: Vec<Box<dyn Tool>> = vec![
    // ...existing tools...
    Box::new(MyTool),
];

That's all. The toolbar discovers tools from the Vec automatically and assigns keyboard shortcuts 1–5 in order.

Optional trait methods to override:

Method Purpose
on_activate() Called when this tool is selected
on_deactivate() Called when switching away — discard in-progress state here
on_confirm() Called on Enter — commit pending state (e.g. text)
captures_keyboard() -> bool Return true while consuming keyboard input; suppresses Space, Delete, Backspace, and number shortcuts in the main loop
options_ui() Render extra controls inside the hold popup

Emitting canvas changes — use CanvasCommand:

// Add a stroke
CanvasCommand::AddStroke { kind: StrokeKind::Pen { .. }, time, fading }

// Erase strokes near a point
CanvasCommand::EraseAt { pos, radius }

// Undo / clear
CanvasCommand::Undo
CanvasCommand::Clear

Icon rendering note. egui's embedded fonts cover ASCII, Latin Extended, and Geometric Shapes (U+25A0–U+25FF). Emoji (U+1F000+) and Dingbats (U+2700–U+27BF) are unreliable — use ASCII characters for icons.

Adding a New Canvas Stroke Type

Add a variant to StrokeKind in canvas.rs, then handle it in Canvas::render(). Look at the existing Pen, Highlighter, Shape, and Text variants for reference.

Persisting New Settings

Add a field to Settings in settings.rs. Fields that may be absent in existing JSON files must use #[serde(default)]. Mirror the field in from_state() and to_draw_style() (or wherever it maps).


Making a Release

Requires the GitHub CLI (gh).

make release automatically reads the current version from Cargo.toml, increments the patch number, writes it back, builds the binary, and prints the next steps.

make release            # 0.1.0 → 0.1.1  (patch bump, default)
make release BUMP=minor # 0.1.x → 0.2.0
make release BUMP=major # 0.x.x → 1.0.0

Then follow the printed next steps (copy-paste ready):

git add Cargo.toml Cargo.lock
git commit -m "chore: release v0.1.1"
git tag v0.1.1 && git push origin main --tags
gh release create v0.1.1 release/rabisco release/rabisco.sha256 --title "v0.1.1"

The release binary lives in release/ (gitignored) and is only uploaded as a GitHub Release asset — it is never committed to the repository.


Contributing

Contributions are welcome. The project follows standard Rust conventions.

Workflow

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Make your changes and verify the build is clean:
    cargo build
    cargo clippy -- -D warnings
  4. Commit with a descriptive message
  5. Open a pull request against main

Pull Request Guidelines

  • Keep PRs focused — one feature or fix per PR
  • PRs that add a new tool should include the tool registered in app.rs and tested manually against X11 and Wayland if possible
  • Icon characters must be ASCII or confirmed to render in egui's default fonts
  • New Settings fields need #[serde(default)] for backwards compatibility
  • No new dependencies without prior discussion in an issue

Support ☕

If Rabisco has helped you give better presentations or saved you some time, consider fueling its development with a coffee! It's completely optional, but always deeply appreciated.

0x89F2d3AF38990478C43e0c47a57E7e62b330b86A J6d3y3865LXFa5fyK39V7xjx6vnRmdhQHkYjJirKNkJq
0x89F2d3AF38990478C43e0c47a57E7e62b330b86A

About

A lightweight on-screen annotation tool in Rust.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors