Files
figcraft/crates/engine/src/textpreview.rs
T
Ahmed5Emad 2cac36a4e2 Live font preview on hover in font picker (#653)
Hovering a family in the Typography font dropdown previews it on the
canvas via text.preview; closing without picking restores the original,
clicking applies it as one undo step.
2026-10-16 12:31:50 +03:00

68 lines
2.9 KiB
Rust

//! Live text previews: what a text command would do, shown on the canvas without an undo step —
//! while the pointer is over a font family or style, a text style in the style browser or a
//! font's named instance, or while a variable font axis slider is dragged. The document under the preview is kept and comes
//! back when the preview ends ([`end`], `text.endPreview`). Every other command ends it first, so
//! committing (running the command for real) applies to the document itself as one undo step, and
//! Escape just ends the preview.
use std::sync::Arc;
use serde_json::Value;
use crate::{EngineError, Result, Session, bad, find_command, guard, normalize};
/// The commands that show or end a preview (they don't end the one showing).
pub const COMMANDS: [&str; 2] = ["text.preview", "text.endPreview"];
/// What can be previewed: text styles, font styles and axis values.
pub const PREVIEWABLE: [&str; 4] = ["style.apply", "library.applyStyle", "text.setVariations", "text.style"];
/// Show `command params` as a live preview (in place of an earlier one).
pub fn show(s: &mut Session, command: &str, params: &Value) -> Result<()> {
let cmd = "text.preview";
if !PREVIEWABLE.contains(&command) {
return Err(bad(cmd, format!("`{command}` can't be previewed (one of: {})", PREVIEWABLE.join(", "))));
}
let spec = find_command(command).ok_or_else(|| EngineError::UnknownCommand(command.to_string()))?;
end(s);
let ds = s.ds()?;
if ds.preview.is_some() {
return Err(EngineError::Disabled(cmd.into(), crate::PREVIEW_READ_ONLY.into()));
}
let (base, selection, text_edit, page, uid) = (ds.doc.clone(), ds.selection.clone(), ds.text_edit.clone(), ds.page, ds.uid);
let result = guard::run(command, || (spec.run)(s, params))
// Library copies and instance structure stay as they are, in previews too.
.and_then(|v| s.check_instance_structure(command, &Some((base.clone(), selection.clone(), page, uid))).map(|()| v));
let ds = s.ds_mut()?;
// A preview changes what's shown, never the selection or the text being edited.
ds.selection = selection;
ds.text_edit = text_edit;
match result {
Ok(_) if !Arc::ptr_eq(&ds.doc, &base) => {
normalize(ds.doc_mut(), &base);
ds.text_preview = Some(base);
ds.revision += 1;
Ok(())
}
Ok(_) => Ok(()),
Err(e) => {
ds.doc = base;
Err(e)
}
}
}
/// End the live preview, if any: the document is back as it was. Returns whether one was showing.
pub fn end(s: &mut Session) -> bool {
let Ok(ds) = s.ds_mut() else { return false };
let Some(base) = ds.text_preview.take() else { return false };
ds.doc = base;
ds.revision += 1;
true
}
/// Whether a live text preview is showing.
pub fn active(s: &Session) -> bool {
s.ds().is_ok_and(|d| d.text_preview.is_some())
}