mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 04:07:12 -06:00
refactor(engine): move lib.rs items to {command_sets,doc_state,error,history,interaction,prefs,sanity,session,session/documents,session/edit,session/execute,session/undo}.rs
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
use super::*;
|
||||
|
||||
impl Session {
|
||||
/// A session with no document open, the default preferences and the Selection tool. It also
|
||||
/// installs the hooks the document crate calls back into: the loader and file reader of placed
|
||||
/// documents, and where an image's effects draw its fills and strokes, for clicks.
|
||||
pub fn new() -> Self {
|
||||
// Placed documents are read by the native format's loader.
|
||||
vectorcraft_doc::placed_document::set_loader(cmd::place::document::read_document);
|
||||
vectorcraft_doc::placed_document::set_file_reader(cmd::place::document::read_file_again);
|
||||
vectorcraft_doc::placed_document::set_read_check(|path| file_access::check_read(path).is_ok());
|
||||
// The background workers of placed documents read files in the scope that asked.
|
||||
vectorcraft_render::placed_document::set_scope_capture(|| {
|
||||
let scope = file_access::ScopeHandle::capture();
|
||||
Box::new(move |f: &mut dyn FnMut()| scope.run(f))
|
||||
});
|
||||
// Clicks find an image's own fills and strokes where their effects draw them.
|
||||
vectorcraft_doc::hit::set_image_paint(vectorcraft_render::effects::image_paint);
|
||||
// OpenType SVG glyphs are drawn as the SVG importer reads their documents.
|
||||
vectorcraft_text::set_svg_glyph_reader(vectorcraft_svg::glyph_art);
|
||||
Self {
|
||||
docs: vec![],
|
||||
active: None,
|
||||
prefs: Prefs::default(),
|
||||
paint: PaintDefaults::default(),
|
||||
fill_active: true,
|
||||
clipboard: Clipboard::default(),
|
||||
journal: Journal::default(),
|
||||
tool: vectorcraft_tools::create("selection"),
|
||||
lender: None,
|
||||
last_selection_tool: None,
|
||||
last_view: ViewInfo::default(),
|
||||
preview_memo: Default::default(),
|
||||
depth: 0,
|
||||
tool_panic: None,
|
||||
tool_resets: 0,
|
||||
tool_set_since_reset: false,
|
||||
draw_mode: DrawMode::Normal,
|
||||
draw_inside: None,
|
||||
untitled_counter: 0,
|
||||
menu: Default::default(),
|
||||
gradient_stop: None,
|
||||
active_appearance_item: None,
|
||||
last_solid: Color::WHITE,
|
||||
last_gradient: GradientPaint::new(Default::default()),
|
||||
recent_colors: vec![],
|
||||
pending_paint: None,
|
||||
swatch_libraries: Default::default(),
|
||||
freeform_point: None,
|
||||
style_libraries: Default::default(),
|
||||
symbol_libraries: Default::default(),
|
||||
brush_libraries: Default::default(),
|
||||
libraries: Default::default(),
|
||||
pdf_preset_dir: None,
|
||||
profile_dir: None,
|
||||
plugin_dir: None,
|
||||
plugin_output: None,
|
||||
recent_urls: vec![],
|
||||
ui_language: None,
|
||||
journal_note: Default::default(),
|
||||
note_depth: 1,
|
||||
journal_query: false,
|
||||
explicit_undo_group: None,
|
||||
batch_stash: None,
|
||||
batch_steps: None,
|
||||
place_hand_off: None,
|
||||
queue_hand_off: None,
|
||||
place_cursor_requests: false,
|
||||
package_hand_off: None,
|
||||
screens_hand_off: None,
|
||||
recovery: Default::default(),
|
||||
in_tool_actions: false,
|
||||
previewing: false,
|
||||
envelope_defaults: None,
|
||||
liquify_stroke: None,
|
||||
plane_widget_press: false,
|
||||
pointer_held: false,
|
||||
ruler_guide: None,
|
||||
port_marks: Default::default(),
|
||||
font_search: None,
|
||||
link_search: None,
|
||||
search_rules: None,
|
||||
deferred_overlay_cache: None,
|
||||
search_threads: None,
|
||||
link_stamps: Default::default(),
|
||||
link_scan: None,
|
||||
global_edit_options: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn documents(&self) -> &[DocState] {
|
||||
&self.docs
|
||||
}
|
||||
|
||||
/// The open document with [`DocState::uid`] `uid` (it may have closed since it was looked up).
|
||||
pub fn document_mut(&mut self, uid: u64) -> Option<&mut DocState> {
|
||||
self.docs.iter_mut().find(|d| d.uid == uid)
|
||||
}
|
||||
|
||||
pub fn active_index(&self) -> Option<usize> {
|
||||
self.active
|
||||
}
|
||||
|
||||
pub fn active(&self) -> Option<&DocState> {
|
||||
self.active.and_then(|i| self.docs.get(i))
|
||||
}
|
||||
|
||||
pub fn active_mut(&mut self) -> Option<&mut DocState> {
|
||||
self.active.and_then(|i| self.docs.get_mut(i))
|
||||
}
|
||||
|
||||
pub fn doc(&self) -> Result<&DocState> {
|
||||
self.active().ok_or(EngineError::NoDocument)
|
||||
}
|
||||
|
||||
pub fn doc_mut(&mut self) -> Result<&mut DocState> {
|
||||
self.active_mut().ok_or(EngineError::NoDocument)
|
||||
}
|
||||
|
||||
/// Finish the tool's pending work in the current document and give it fresh state, so nothing
|
||||
/// (e.g. uncommitted typing) leaks into the document we are switching to.
|
||||
fn reset_tool_for_doc_switch(&mut self) {
|
||||
if self.active.is_none() {
|
||||
return;
|
||||
}
|
||||
let view = self.last_view;
|
||||
// The switch goes on whatever the lent tool's last actions did, as for the deactivation.
|
||||
let _ = self.give_back_tool(view);
|
||||
let acts = self.with_tool_cx(view, |t, cx| t.deactivate(cx));
|
||||
let _ = self.apply_actions(acts);
|
||||
// A batch leaves its interaction open in the document it leaves, to keep or roll back with
|
||||
// the rest of the batch; anything else in progress (a drag) is cancelled.
|
||||
if self.batch_stash.is_none() {
|
||||
let _ = self.cancel_interaction();
|
||||
}
|
||||
self.keep_tool_settings();
|
||||
self.tool = self.make_tool(self.tool.id());
|
||||
self.tool_resets = self.tool_resets.wrapping_add(1);
|
||||
self.tool_set_since_reset = false;
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, index: usize) -> bool {
|
||||
if index < self.docs.len() {
|
||||
if self.active != Some(index) {
|
||||
self.reset_tool_for_doc_switch();
|
||||
}
|
||||
self.active = Some(index);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The name [`Session::next_untitled`] gives next (New Document's Name field).
|
||||
pub fn peek_untitled(&self) -> String {
|
||||
format!("Untitled-{}", self.untitled_counter + 1)
|
||||
}
|
||||
|
||||
pub fn next_untitled(&mut self) -> String {
|
||||
self.untitled_counter += 1;
|
||||
format!("Untitled-{}", self.untitled_counter)
|
||||
}
|
||||
|
||||
/// Text layout bounds are a cache (not saved): compute them for a document just read, or
|
||||
/// selection boxes and hit testing would use the rough estimate until each text is edited, and
|
||||
/// what is fitted to type's bounds (an envelope warping it) would come out differently. That
|
||||
/// holds for the type in symbol definitions, pattern tiles, graph designs and opacity masks too,
|
||||
/// not only in the layers (#1738).
|
||||
pub(crate) fn refresh_text_bounds(doc: &mut Document) {
|
||||
// Inline graphics in text take their size from their symbols' art (not saved either).
|
||||
doc.resolve_inline_art();
|
||||
let trees = doc.layers.iter_mut().chain(doc.symbols.iter_mut().map(|s| &mut s.art));
|
||||
let trees = trees.chain(doc.graph_designs.iter_mut().map(|g| &mut g.art));
|
||||
for tree in trees.chain(doc.patterns.iter_mut().flat_map(|p| p.art.iter_mut())) {
|
||||
refresh_tree_text_bounds(tree);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a document and make it active. Between `edit.undoGroup.begin` and `edit.undoGroup.end`,
|
||||
/// an undo group with the begun group's name opens in it: here outside a command, and after the
|
||||
/// command that adds it otherwise ([`Session::execute`]).
|
||||
pub fn add_document(&mut self, mut doc: Document, path: Option<String>) -> usize {
|
||||
// Every edit is checked against the document before it, so the first one is within the cap
|
||||
// (importers and the native reader have seen to theirs already, with a warning).
|
||||
doc.limit_depth();
|
||||
Self::refresh_text_bounds(&mut doc);
|
||||
// Guides from files saved before guides had layers go on a layer.
|
||||
doc.adopt_guides();
|
||||
cmd::dimension::conform(&mut doc);
|
||||
self.reset_tool_for_doc_switch();
|
||||
let mut st = DocState::new(doc, path);
|
||||
st.history.limit = self.prefs.history_states as usize;
|
||||
self.docs.push(st);
|
||||
let i = self.docs.len() - 1;
|
||||
self.active = Some(i);
|
||||
if self.depth == 0
|
||||
&& let Some(label) = self.explicit_undo_group.clone()
|
||||
{
|
||||
self.begin_named_undo_group(label);
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
/// Replace the document in tab `index` (File → Revert): new content, cleared history and
|
||||
/// selection, saved state. The tab keeps its place, path, format and view.
|
||||
pub fn replace_document(&mut self, index: usize, mut doc: Document) -> bool {
|
||||
if index >= self.docs.len() {
|
||||
return false;
|
||||
}
|
||||
doc.limit_depth();
|
||||
Self::refresh_text_bounds(&mut doc);
|
||||
doc.adopt_guides();
|
||||
cmd::dimension::conform(&mut doc);
|
||||
if self.active == Some(index) {
|
||||
// Pending tool work (typing, a drag) belongs to the content being thrown away.
|
||||
self.reset_tool_for_doc_switch();
|
||||
}
|
||||
let old = &self.docs[index];
|
||||
let mut st = DocState::new(doc, old.path.clone());
|
||||
st.history.limit = old.history.limit;
|
||||
// Same open document (caches keyed by uid stay valid); a new revision redraws it.
|
||||
st.uid = old.uid;
|
||||
st.revision = old.revision + 1;
|
||||
st.format = old.format;
|
||||
st.save_options = old.save_options.clone();
|
||||
// The remembered artboard pick follows the artboards it named when the file read back has
|
||||
// the artboards the document had at the save, in the same order and with the same ids (a
|
||||
// native file keeps them; a PDF's pages get new ids in page order); otherwise it is left
|
||||
// out.
|
||||
if old.save_artboards.is_empty() || st.doc.artboards.iter().map(|a| a.id).eq(old.save_artboards.iter().copied()) {
|
||||
st.save_artboards = old.save_artboards.clone();
|
||||
} else {
|
||||
for k in cmd::fileio::ARTBOARD_PARAMS {
|
||||
st.save_options.remove(k);
|
||||
}
|
||||
}
|
||||
st.converted = old.converted;
|
||||
st.imported_from = old.imported_from.clone();
|
||||
st.import_losses = old.import_losses.clone();
|
||||
st.view = old.view.clone();
|
||||
st.layers_open = old.layers_open.clone();
|
||||
let old = std::mem::replace(&mut self.docs[index], st);
|
||||
if let Some(stash) = &mut self.batch_stash {
|
||||
stash.push(old);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn close_document(&mut self, index: usize) -> bool {
|
||||
if index >= self.docs.len() {
|
||||
return false;
|
||||
}
|
||||
self.reset_tool_for_doc_switch();
|
||||
// Closed (saved or discarded): nothing left to recover.
|
||||
let uid = self.docs[index].uid;
|
||||
cmd::recovery::forget(self, uid);
|
||||
let old = self.docs.remove(index);
|
||||
if let Some(stash) = &mut self.batch_stash {
|
||||
stash.push(old);
|
||||
}
|
||||
self.active = if self.docs.is_empty() { None } else { Some(index.min(self.docs.len() - 1)) };
|
||||
true
|
||||
}
|
||||
|
||||
/// Commands with enablement (for menus, palette, MCP `list_commands`).
|
||||
pub fn commands(&self) -> Vec<CommandInfo> {
|
||||
command_specs().iter().map(|c| c.info(self)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Lay out the type in tree `n` (opacity mask art included) whose bounds aren't cached. Only the
|
||||
/// nodes on the way to such type are copied.
|
||||
fn refresh_tree_text_bounds(n: &mut Arc<vectorcraft_doc::Node>) {
|
||||
fn stale(n: &vectorcraft_doc::Node) -> bool {
|
||||
matches!(&n.kind, NodeKind::Text(t) if t.cached_bounds.is_none())
|
||||
|| n.extras().mask.as_deref().is_some_and(|m| stale(&m.art))
|
||||
|| n.children().is_some_and(|ch| ch.iter().any(|c| stale(c)))
|
||||
}
|
||||
if !stale(n) {
|
||||
return;
|
||||
}
|
||||
let n = Arc::make_mut(n);
|
||||
if let NodeKind::Text(t) = &mut n.kind
|
||||
&& t.cached_bounds.is_none()
|
||||
{
|
||||
cmd::typecmd::refresh_bounds(t);
|
||||
}
|
||||
if let Some(m) = n.extras_if_any().and_then(|e| e.mask.as_deref_mut()) {
|
||||
refresh_tree_text_bounds(&mut m.art);
|
||||
}
|
||||
for c in n.children_mut().into_iter().flatten() {
|
||||
refresh_tree_text_bounds(c);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user