mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-26 11:26:50 -06:00
- Cursors: the engine names a cursor for every tool, modifier and hover target (rotate turned to its corner, pen states, eyedropper, comment, copy on Alt, not-allowed while an instance refuses the edit); the UI shows the system's or draws its own; guides and handles keep their cursors; ui.inspect reports it. - Panels: each edge drags its panel within limits, a double-click resets, widths are per window and kept in the preferences (view.panelWidths); a small window keeps its canvas. - Outline mode: Include hidden layers and Include object bounds (view.outlinesHiddenLayers, view.outlinesObjectBounds), drawn and clickable; light lines on a dark page; check marks in the View menu. - A file or page made in the dark theme has a #1E1E1E page (sessions without an interface keep #F5F5F5); text drawn on a dark page is white. - Tests for Enter and Escape on a selected text.
77 lines
3.1 KiB
Rust
77 lines
3.1 KiB
Rust
//! Pattern fills: the source layer drawn into one repeating cell at the resolution it shows at,
|
||
//! repeated by a tiling shader.
|
||
|
||
use std::cell::Cell;
|
||
use std::sync::Arc;
|
||
|
||
use figcraft_color::{PatternCell, PatternPaint};
|
||
use figcraft_doc::{Document, NodeId};
|
||
use figcraft_geom::Affine;
|
||
use tiny_skia::{FilterQuality, Pixmap, SpreadMode, Transform};
|
||
|
||
use crate::draw::{Ctx, draw_node};
|
||
use crate::paint::Prepared;
|
||
|
||
/// The largest side of a cell's pixmap; a cell showing larger is drawn coarser.
|
||
const MAX_CELL_PX: f64 = 2048.0;
|
||
/// How deep patterns nest (a source filled with a pattern); deeper ones, and a source filled
|
||
/// with its own pattern, draw nothing.
|
||
pub const MAX_DEPTH: u32 = 2;
|
||
|
||
thread_local! {
|
||
static DEPTH: Cell<u32> = const { Cell::new(0) };
|
||
}
|
||
|
||
/// Run `f` one pattern level deeper; `None` past [`MAX_DEPTH`].
|
||
pub fn nested<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
|
||
let d = DEPTH.with(Cell::get);
|
||
if d >= MAX_DEPTH {
|
||
return None;
|
||
}
|
||
DEPTH.with(|c| c.set(d + 1));
|
||
let out = f();
|
||
DEPTH.with(|c| c.set(d));
|
||
out
|
||
}
|
||
|
||
/// Pattern `pt` on a `w`×`h` box drawn into one cell at about `scale` device pixels per unit and
|
||
/// `time` seconds into animated GIFs:
|
||
/// the pixmap, the cell and the pixmap's pixels per unit on each axis (whole pixels per cell, so
|
||
/// the repeat lines up exactly).
|
||
pub fn cell_pixmap(doc: &Document, pt: &PatternPaint, w: f64, h: f64, scale: f64, time: f64) -> Option<(Pixmap, PatternCell, (f64, f64))> {
|
||
let src = doc.node(NodeId(pt.source)).filter(|n| !n.is_page())?;
|
||
let cell = pt.cell(src.width, src.height, w, h)?;
|
||
let scale = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
|
||
let k = scale.min(MAX_CELL_PX / cell.size.0.max(cell.size.1));
|
||
let (pw, ph) = ((cell.size.0 * k).ceil().clamp(1.0, MAX_CELL_PX), (cell.size.1 * k).ceil().clamp(1.0, MAX_CELL_PX));
|
||
let (kx, ky) = (pw / cell.size.0, ph / cell.size.1);
|
||
let mut pix = Pixmap::new(pw as u32, ph as u32)?;
|
||
let ctx = Ctx {
|
||
doc,
|
||
scale: kx.max(ky) * cell.scale,
|
||
outline_mode: false,
|
||
outline: crate::Outline::default(),
|
||
outlined: &[],
|
||
skip: &[],
|
||
lod: false,
|
||
under: None,
|
||
time,
|
||
};
|
||
let unrotated = src.transform.inverse();
|
||
nested(|| {
|
||
for (tx, ty) in &cell.tiles {
|
||
let xf = Affine::scale_non_uniform(kx, ky) * Affine::translate((*tx, *ty)) * Affine::scale(cell.scale) * unrotated;
|
||
draw_node(&ctx, &mut pix, src.id, xf, 0);
|
||
}
|
||
Some(())
|
||
})?;
|
||
Some((pix, cell, (kx, ky)))
|
||
}
|
||
|
||
/// The shader for pattern `pt` on a `w`×`h` box shown at `scale` device pixels per unit.
|
||
pub fn prepare(doc: &Document, pt: &PatternPaint, w: f64, h: f64, scale: f64, time: f64, opacity: f32) -> Option<Prepared> {
|
||
let (pix, cell, (kx, ky)) = cell_pixmap(doc, pt, w, h, scale, time)?;
|
||
let transform = Transform::from_row((1.0 / kx) as f32, 0.0, 0.0, (1.0 / ky) as f32, cell.origin.0 as f32, cell.origin.1 as f32);
|
||
Some(Prepared::Pattern { pixmap: Arc::new(pix), mode: SpreadMode::Repeat, quality: FilterQuality::Bilinear, opacity, transform })
|
||
}
|