Files
effectcraft/crates/ui-egui/src/state.rs
T
nazar-gavrylyk 87bfaa8f1c Preview: Cache Frames When Idle is on by default, as in After Effects
A comp left alone for a second fills its RAM preview from the current time,
so the first playback is cached instead of rendering frame by frame. Saved
settings keep their value. Two UI tests that assumed the old default set it
explicitly.
2026-11-10 15:03:10 +01:00

867 lines
33 KiB
Rust

//! Frontend view state (not project data). Serde so the control channel can read and set it.
use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use crate::dock::PanelKind;
use crate::icons::Icon;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Tool {
#[default]
Selection,
Hand,
Zoom,
Orbit,
PanCamera,
Dolly,
Rotate,
PanBehind,
Rectangle,
RoundedRect,
Ellipse,
Polygon,
Star,
Pen,
PenAdd,
PenDelete,
PenConvert,
MaskFeather,
Type,
TypeVertical,
Brush,
Clone,
Eraser,
RotoBrush,
RefineEdge,
/// Object Matte: click an object (or drag a box around it) in the Composition viewer.
ObjectMatte,
Puppet,
PuppetStarch,
PuppetBend,
PuppetAdvanced,
PuppetOverlap,
}
impl Tool {
/// Toolbar slots: each slot shows its current tool; the slot cycles with its shortcut.
pub const SLOTS: &'static [&'static [Tool]] = &[
&[Tool::Selection],
&[Tool::Hand],
&[Tool::Zoom],
&[Tool::Orbit],
&[Tool::PanCamera],
&[Tool::Dolly],
&[Tool::Rotate],
&[Tool::PanBehind],
&[Tool::Rectangle, Tool::RoundedRect, Tool::Ellipse, Tool::Polygon, Tool::Star],
&[Tool::Pen, Tool::PenAdd, Tool::PenDelete, Tool::PenConvert, Tool::MaskFeather],
&[Tool::Type, Tool::TypeVertical],
&[Tool::Brush],
&[Tool::Clone],
&[Tool::Eraser],
&[Tool::RotoBrush, Tool::RefineEdge, Tool::ObjectMatte],
&[Tool::Puppet, Tool::PuppetStarch, Tool::PuppetBend, Tool::PuppetAdvanced, Tool::PuppetOverlap],
];
pub const ALL: [Tool; 31] = [
Tool::Selection,
Tool::Hand,
Tool::Zoom,
Tool::Orbit,
Tool::PanCamera,
Tool::Dolly,
Tool::Rotate,
Tool::PanBehind,
Tool::Rectangle,
Tool::RoundedRect,
Tool::Ellipse,
Tool::Polygon,
Tool::Star,
Tool::Pen,
Tool::PenAdd,
Tool::PenDelete,
Tool::PenConvert,
Tool::MaskFeather,
Tool::Type,
Tool::TypeVertical,
Tool::Brush,
Tool::Clone,
Tool::Eraser,
Tool::RotoBrush,
Tool::RefineEdge,
Tool::ObjectMatte,
Tool::Puppet,
Tool::PuppetStarch,
Tool::PuppetBend,
Tool::PuppetAdvanced,
Tool::PuppetOverlap,
];
pub fn label(self) -> &'static str {
match self {
Tool::Selection => "Selection Tool",
Tool::Hand => "Hand Tool",
Tool::Zoom => "Zoom Tool",
Tool::Orbit => "Orbit Around Cursor Tool",
Tool::PanCamera => "Pan Under Cursor Tool",
Tool::Dolly => "Dolly Towards Cursor Tool",
Tool::Rotate => "Rotation Tool",
Tool::PanBehind => "Pan Behind (Anchor Point) Tool",
Tool::Rectangle => "Rectangle Tool",
Tool::RoundedRect => "Rounded Rectangle Tool",
Tool::Ellipse => "Ellipse Tool",
Tool::Polygon => "Polygon Tool",
Tool::Star => "Star Tool",
Tool::Pen => "Pen Tool",
Tool::PenAdd => "Add Vertex Tool",
Tool::PenDelete => "Delete Vertex Tool",
Tool::PenConvert => "Convert Vertex Tool",
Tool::MaskFeather => "Mask Feather Tool",
Tool::Type => "Horizontal Type Tool",
Tool::TypeVertical => "Vertical Type Tool",
Tool::Brush => "Brush Tool",
Tool::Clone => "Clone Stamp Tool",
Tool::Eraser => "Eraser Tool",
Tool::RotoBrush => "Roto Brush Tool",
Tool::RefineEdge => "Refine Edge Tool",
Tool::ObjectMatte => "Object Matte Tool",
Tool::Puppet => "Puppet Position Pin Tool",
Tool::PuppetStarch => "Puppet Starch Pin Tool",
Tool::PuppetBend => "Puppet Bend Pin Tool",
Tool::PuppetAdvanced => "Puppet Advanced Pin Tool",
Tool::PuppetOverlap => "Puppet Overlap Pin Tool",
}
}
pub fn shortcut(self) -> Option<&'static str> {
match self {
Tool::Selection => Some("V"),
Tool::Hand => Some("H"),
Tool::Zoom => Some("Z"),
Tool::Orbit => Some("1"),
Tool::PanCamera => Some("2"),
Tool::Dolly => Some("3"),
Tool::Rotate => Some("W"),
Tool::PanBehind => Some("Y"),
Tool::Rectangle | Tool::RoundedRect | Tool::Ellipse | Tool::Polygon | Tool::Star => Some("Q"),
Tool::Pen | Tool::PenAdd | Tool::PenDelete | Tool::PenConvert | Tool::MaskFeather => Some("G"),
Tool::Type | Tool::TypeVertical => Some("Cmd+T"),
Tool::Brush | Tool::Clone | Tool::Eraser => Some("Cmd+B"),
Tool::RotoBrush | Tool::RefineEdge | Tool::ObjectMatte => Some("Alt+W"),
Tool::Puppet | Tool::PuppetStarch | Tool::PuppetBend | Tool::PuppetAdvanced | Tool::PuppetOverlap => Some("Cmd+P"),
}
}
pub fn icon(self) -> Icon {
match self {
Tool::Selection => Icon::Selection,
Tool::Hand => Icon::Hand,
Tool::Zoom => Icon::Zoom,
Tool::Orbit => Icon::Orbit,
Tool::PanCamera => Icon::PanCamera,
Tool::Dolly => Icon::Dolly,
Tool::Rotate => Icon::Rotate,
Tool::PanBehind => Icon::PanBehind,
Tool::Rectangle => Icon::Rectangle,
Tool::RoundedRect => Icon::RoundedRect,
Tool::Ellipse => Icon::Ellipse,
Tool::Polygon => Icon::Polygon,
Tool::Star => Icon::Star,
Tool::Pen => Icon::Pen,
Tool::PenAdd => Icon::PenAdd,
Tool::PenDelete => Icon::PenDelete,
Tool::PenConvert => Icon::PenConvert,
Tool::MaskFeather => Icon::MaskFeather,
Tool::Type => Icon::Type,
Tool::TypeVertical => Icon::TypeVertical,
Tool::Brush => Icon::Brush,
Tool::Clone => Icon::Clone,
Tool::Eraser => Icon::Eraser,
Tool::RotoBrush => Icon::RotoBrush,
Tool::RefineEdge => Icon::RefineEdge,
Tool::ObjectMatte => Icon::ObjectMatte,
Tool::Puppet => Icon::Puppet,
Tool::PuppetStarch => Icon::PuppetStarch,
Tool::PuppetBend => Icon::PuppetBend,
Tool::PuppetAdvanced => Icon::PuppetAdvanced,
Tool::PuppetOverlap => Icon::PuppetOverlap,
}
}
pub fn from_name(s: &str) -> Option<Tool> {
let n = s.to_ascii_lowercase().replace([' ', '_', '-'], "");
Tool::ALL.into_iter().find(|t| format!("{t:?}").to_ascii_lowercase() == n || t.label().to_ascii_lowercase().replace(' ', "").starts_with(&n))
}
/// Puppet pin tools, with the pin kind they place (`puppet.addPin` kind).
pub fn puppet_kind(self) -> Option<&'static str> {
Some(match self {
Tool::Puppet => "position",
Tool::PuppetStarch => "starch",
Tool::PuppetBend => "bend",
Tool::PuppetAdvanced => "advanced",
Tool::PuppetOverlap => "overlap",
_ => return None,
})
}
/// Roto Brush / Refine Edge: the stroke kind they paint (`roto.stroke` kind), without and
/// with Alt/Option.
pub fn roto_kind(self, alt: bool) -> Option<&'static str> {
Some(match (self, alt) {
(Tool::RotoBrush, false) => "fg",
(Tool::RotoBrush, true) => "bg",
(Tool::RefineEdge, false) => "refine",
(Tool::RefineEdge, true) => "refineErase",
_ => return None,
})
}
/// Paint tools, with their stroke kind (`paint.stroke` kind).
pub fn paint_kind(self) -> Option<&'static str> {
Some(match self {
Tool::Brush => "brush",
Tool::Clone => "clone",
Tool::Eraser => "eraser",
_ => return None,
})
}
/// The Pen slot's tools (Pen, Add / Delete / Convert Vertex, Mask Feather).
pub fn is_pen(self) -> bool {
matches!(self, Tool::Pen | Tool::PenAdd | Tool::PenDelete | Tool::PenConvert | Tool::MaskFeather)
}
pub fn is_shape(self) -> bool {
matches!(self, Tool::Rectangle | Tool::RoundedRect | Tool::Ellipse | Tool::Polygon | Tool::Star)
}
}
use super::*;
/// The viewer's Resolution: each composition keeps its own in the project
/// ([`effectcraft_engine::project::Comp::resolution`], `EffectcraftApp::viewer_res`).
pub use effectcraft_engine::project::Resolution;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ViewerState {
/// Magnification (1 = 100%); None = fit.
pub zoom: Option<f32>,
/// Fit up to 100%: while fitting (`zoom` is None), the comp is never magnified past 100%.
pub fit_up_to_100: bool,
/// Pan offset in points from the centred position.
pub pan: [f32; 2],
pub transparency_grid: bool,
pub show_masks: bool,
pub safe_margins: bool,
pub grid: bool,
pub rulers: bool,
pub channel: String,
pub show_layer_controls: bool,
pub fast_preview: bool,
/// View ▸ Show Guides / Snap to Guides / Lock Guides / Snap to Grid.
pub guides: bool,
pub snap_guides: bool,
pub lock_guides: bool,
pub snap_grid: bool,
/// Grid and guide options ▸ Proportional Grid.
pub proportional_grid: bool,
/// Rulers zero point (comp pixels), moved by dragging the ruler corner.
pub ruler_origin: [f64; 2],
/// Region of Interest button armed: the next drag in the viewer draws the region.
pub roi_draw: bool,
/// The pointer is dragging in the viewer this frame (Adaptive Resolution).
#[serde(skip)]
pub interacting: bool,
#[serde(skip)]
pub property_interacting: bool,
/// Extended Viewer: the comp-space region the viewer renders this frame (comp frame plus
/// the visible pasteboard), set while a 3D view shows past the frame.
#[serde(skip)]
pub extended: Option<[f64; 4]>,
/// View ▸ Panel Background Color (`None` = theme default).
pub pasteboard: Option<[u8; 3]>,
pub custom_pasteboard: [u8; 3],
}
impl Default for ViewerState {
fn default() -> Self {
ViewerState {
zoom: None,
fit_up_to_100: false,
pan: [0.0, 0.0],
transparency_grid: false,
show_masks: true,
safe_margins: false,
grid: false,
rulers: false,
channel: "RGB".into(),
show_layer_controls: true,
fast_preview: false,
guides: true,
snap_guides: false,
lock_guides: false,
snap_grid: false,
proportional_grid: false,
ruler_origin: [0.0, 0.0],
roi_draw: false,
interacting: false,
property_interacting: false,
extended: None,
pasteboard: None,
custom_pasteboard: [0x80, 0x80, 0x80],
}
}
}
impl TimelineState {
/// Whether property `uid`'s chain link (Constrain Proportions) is on. One that starts linked
/// (Scale, Mask Feather) records a click in `unlinked`; one that starts unlinked (shape Size,
/// Audio Levels) records it in `linked`.
pub fn is_linked(&self, uid: u64, linked_by_default: bool) -> bool {
if linked_by_default { !self.unlinked.contains(&uid) } else { self.linked.contains(&uid) }
}
/// Turn property `uid`'s chain link on or off (see [`Self::is_linked`]).
pub fn toggle_link(&mut self, uid: u64, linked_by_default: bool) {
let set = if linked_by_default { &mut self.unlinked } else { &mut self.linked };
if !set.remove(&uid) {
set.insert(uid);
}
}
/// Forget the chain links when the project was replaced (new or opened): they are keyed by
/// property uid, which the next project reuses, so a link turned on must not carry into it.
pub fn scope_links(&mut self, project_epoch: u64) {
if self.link_epoch != project_epoch {
self.link_epoch = project_epoch;
self.unlinked.clear();
self.linked.clear();
}
}
/// Show `kinds` on `layers` (twirled open; empty: twirled closed) and remember it as the last
/// reveal. Other layers keep theirs.
pub fn apply_reveal(&mut self, layers: &[u64], kinds: Vec<String>) {
for id in layers {
self.full_groups.remove(id);
if kinds.is_empty() {
self.open_layers.remove(id);
self.layer_reveal.remove(id);
} else {
self.open_layers.insert(*id);
self.layer_reveal.insert(*id, kinds.clone());
}
}
self.reveal = kinds;
}
/// Twirl group `uid` of `layer` by hand. Opened under a reveal, it shows all its properties
/// (collapse and expand an effect that shows only its revealed parameters to see them all).
pub fn toggle_group(&mut self, layer: u64, uid: u64) {
if self.open_groups.remove(&uid) {
return;
}
self.open_groups.insert(uid);
if self.layer_reveal.contains_key(&layer) {
self.full_groups.entry(layer).or_default().insert(uid);
}
}
}
/// Where the Timeline scrolls to.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ScrollTo {
/// D: the current time, centred in the time graph.
Time,
/// X: the topmost selected layer, at the top of the layers.
Layer,
/// A revealed property, at the top of the rows.
Property(u64),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TimelineState {
/// Visible span start (seconds) and pixels per second; None = fit comp.
pub start: f64,
pub pps: Option<f64>,
/// The span (`start`, `pps`) Zoom to Fit Comp left, which it goes back to when pressed again.
#[serde(skip)]
pub before_fit: Option<(f64, f64)>,
pub scroll_y: f32,
/// Left column area width.
pub columns_w: f32,
/// Horizontal scroll of the outline columns (pixels) when they are wider than the pane.
#[serde(default)]
pub outline_scroll: f32,
pub show_modes: bool,
pub graph_editor: bool,
pub search: String,
/// Twirled-open layers and groups (by layer id / group uid).
pub open_layers: BTreeSet<u64>,
pub open_groups: BTreeSet<u64>,
/// The last reveal shortcut's filter (P/S/R/T/A…): what pressing it again toggles or adds to.
pub reveal: Vec<String>,
/// Each revealed layer's filter: an open layer without one shows its whole property tree. A
/// reveal shortcut sets it on the selected layers (all layers with none selected) and leaves
/// the others as they are.
#[serde(default)]
pub layer_reveal: BTreeMap<u64, Vec<String>>,
/// Properties / groups (uids) shown by the `props` reveal (Animation ▸ Reveal Properties…).
#[serde(default)]
pub reveal_props: BTreeSet<u64>,
/// Groups of a revealed layer (by layer id) twirled open by hand since the reveal: they show
/// their whole contents, not just the revealed properties.
#[serde(default)]
pub full_groups: BTreeMap<u64, BTreeSet<u64>>,
/// Graph Editor: `auto` (Auto-Select Graph Type, the default: the speed graph when every
/// property shown is spatial, else the value graph), `value` or `speed`.
#[serde(default = "auto_graph")]
pub graph_mode: String,
/// Show only the selected properties (else every animated property of the selected layers).
#[serde(default = "yes")]
pub graph_show_selected: bool,
/// Auto-zoom the graph height to the visible curves.
#[serde(default = "yes")]
pub graph_auto_zoom: bool,
/// Manual graph value range (when auto-zoom is off).
#[serde(default)]
pub graph_range: Option<(f64, f64)>,
/// Graph Editor ▸ Snap: key drags snap to the current time, other keys and frames.
#[serde(default = "yes")]
pub graph_snap: bool,
/// Graph Editor ▸ Show Reference Graph (the other graph type, faint, behind).
#[serde(default)]
pub graph_reference: bool,
/// Graph Editor ▸ Show Transform Box When Multiple Keys Are Selected.
#[serde(default = "yes")]
pub graph_transform_box: bool,
/// Properties whose inline expression editor is collapsed.
#[serde(default)]
pub expr_closed: BTreeSet<u64>,
/// Inline expression editors given a height (in lines) by dragging their bottom edge;
/// the others grow with their text, up to 8 lines.
#[serde(default)]
pub expr_lines: BTreeMap<u64, usize>,
/// Scale and Mask Feather properties whose chain link (Constrain Proportions, on by default)
/// was turned off.
#[serde(default)]
pub unlinked: BTreeSet<u64>,
/// Shape Rectangle and Ellipse Path Size (#2489) and Audio Levels (#2485) properties whose
/// chain link (off by default) was turned on.
#[serde(default)]
pub linked: BTreeSet<u64>,
/// The project (its epoch) `unlinked` and `linked` belong to.
#[serde(skip)]
pub link_epoch: u64,
/// Visible optional columns (column header right-click ▸ Columns): `av`, `keys`, `label`,
/// `num`, `comment`, `switches`, `parent`, `in`, `out`, `duration`, `stretch`. The name
/// column is always shown; Modes follows `show_modes` (F4).
#[serde(default = "default_tl_columns")]
pub columns: BTreeSet<String>,
/// The name column shows Source Name instead of Layer Name (click its header).
#[serde(default)]
pub source_name: bool,
/// A pending scroll, applied when the Timeline is next drawn.
#[serde(skip)]
pub scroll_to: Option<ScrollTo>,
}
pub fn default_tl_columns() -> BTreeSet<String> {
["av", "label", "num", "switches", "parent"].map(String::from).into_iter().collect()
}
fn auto_graph() -> String {
"auto".into()
}
fn yes() -> bool {
true
}
impl Default for TimelineState {
fn default() -> Self {
TimelineState {
start: 0.0,
pps: None,
before_fit: None,
scroll_y: 0.0,
columns_w: 560.0,
outline_scroll: 0.0,
show_modes: false,
graph_editor: false,
search: String::new(),
open_layers: BTreeSet::new(),
open_groups: BTreeSet::new(),
reveal: vec![],
layer_reveal: BTreeMap::new(),
reveal_props: BTreeSet::new(),
full_groups: BTreeMap::new(),
graph_mode: auto_graph(),
graph_show_selected: true,
graph_auto_zoom: true,
graph_range: None,
graph_snap: true,
graph_reference: false,
graph_transform_box: true,
expr_closed: BTreeSet::new(),
expr_lines: BTreeMap::new(),
unlinked: BTreeSet::new(),
linked: BTreeSet::new(),
link_epoch: 0,
columns: default_tl_columns(),
source_name: false,
scroll_to: None,
}
}
}
fn default_project_sort() -> String {
"name".into()
}
pub fn default_project_columns() -> Vec<String> {
["type", "size", "duration", "fps"].map(String::from).to_vec()
}
/// What an Effect Controls crosshair / eyedropper click in the viewer sets.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FxPick {
/// `point` (point parameter, layer space) or `color` (colour sampled from the frame).
pub kind: String,
pub layer: u64,
pub prop: u64,
/// Parameter name (for the viewer hint).
pub name: String,
}
/// A Character panel Fill or Stroke eyedropper waiting for a Composition viewer click.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TextColorPick {
pub layer: u64,
pub field: String,
/// Preserve the edited character range when focus moves from the panel to the viewer.
pub range: Option<[usize; 2]>,
}
/// Effects & Presets contents-menu options.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EffectsView {
/// `all`, `32` (32 bpc effects only) or `gpu` (GPU-accelerated only).
pub depth: String,
/// Show the "* Animation Presets" folder.
pub presets: bool,
/// Flat alphabetical list instead of categories.
pub alphabetical: bool,
}
impl Default for EffectsView {
fn default() -> Self {
EffectsView { depth: "all".into(), presets: true, alphabetical: false }
}
}
/// Wiggler, Smoother and Motion Sketch panel settings.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct AnimToolsState {
/// Wiggler Apply To: Spatial Path (true) or Temporal Path.
pub wiggle_spatial: bool,
/// Noise Type: Smooth (true) or Jagged.
pub wiggle_smooth: bool,
/// `one` (dimension `wiggle_dim`), `same` or `independent`.
pub wiggle_dims: String,
pub wiggle_dim: usize,
pub wiggle_frequency: f64,
pub wiggle_magnitude: f64,
pub smooth_tolerance: f64,
/// Motion Sketch: capture speed (%), smoothing, show wireframe / background.
pub sketch_speed: f64,
pub sketch_smoothing: f64,
pub sketch_wireframe: bool,
pub sketch_background: bool,
/// Start Capture pressed: the next drag in the viewer records.
pub sketch_armed: bool,
}
impl Default for AnimToolsState {
fn default() -> Self {
AnimToolsState {
wiggle_spatial: true,
wiggle_smooth: true,
wiggle_dims: "independent".into(),
wiggle_dim: 0,
wiggle_frequency: 5.0,
wiggle_magnitude: 1.0,
smooth_tolerance: 1.0,
sketch_speed: 100.0,
sketch_smoothing: 1.0,
sketch_wireframe: false,
sketch_background: true,
sketch_armed: false,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ProjectStatus {
#[default]
All,
Available,
Missing,
}
impl ProjectStatus {
pub const ALL: [Self; 3] = [Self::All, Self::Available, Self::Missing];
/// English catalog key; localization belongs at the widget rendering boundary.
pub fn label(self) -> &'static str {
match self {
Self::All => "All",
Self::Available => "Available",
Self::Missing => "Missing",
}
}
/// Filter the dependency inventory, including unused missing proxies. Generated items
/// count as available even though they have no external source file.
pub fn matches(self, item: &effectcraft_engine::project::Item) -> bool {
match self {
Self::All => true,
Self::Available => !item.has_missing_footage(),
Self::Missing => item.has_missing_footage(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UiState {
pub tool: Tool,
/// Window ▸ Script Console input, output and history.
#[serde(default)]
pub script_console: crate::panels::script_console::ScriptConsole,
/// Tool shown in each toolbar slot.
pub slot_tools: Vec<Tool>,
pub workspace: String,
/// The current layout: docked frames, floating windows (one can be recorded as its own
/// native window, Move Panel to New Window) and the places of closed panels.
pub dock: crate::dock::Workspace,
/// Saved workspace layouts (Save Changes to this Workspace / Save as New Workspace).
#[serde(default)]
pub saved_workspaces: std::collections::BTreeMap<String, crate::dock::Workspace>,
/// The panel maximized to fill the dock area (`~`), if any.
#[serde(default)]
pub maximized: Option<PanelKind>,
/// Floating windows of [`Self::dock`] whose native rect holds the OS window's outer
/// position, from a workspace file of an earlier version (see
/// `crate::dock_ui::native_outer_to_content`). Workspaces are not saved while any remain.
#[serde(skip)]
pub native_outer: Vec<u64>,
/// Locked Composition / Timeline tabs (panel ids): opening another composition does not
/// bring a locked panel forward (the tab's lock icon, as After Effects' viewer lock).
#[serde(default)]
pub locked_tabs: BTreeSet<String>,
/// The comp each Composition viewer shows, by viewer id (0 = the Composition panel; see
/// `panels::viewers`).
#[serde(default)]
pub viewers: std::collections::BTreeMap<u32, Option<u64>>,
/// The active viewer (the interactive one, showing the active comp).
#[serde(default)]
pub active_viewer: u32,
pub focused: PanelKind,
pub viewer: ViewerState,
pub timeline: TimelineState,
pub theme: crate::theme::ThemeKind,
pub show_menu_bar: bool,
pub status: String,
/// Effects & Presets search text.
pub effects_search: String,
pub effects_open: BTreeSet<String>,
pub project_search: String,
#[serde(default)]
pub project_status: ProjectStatus,
pub project_open_folders: BTreeSet<u64>,
/// Project panel sort column (`name`, `type`, `size`, `fps`) and direction, as in AE's
/// clickable column headers. Folders sort with everything else.
#[serde(default = "default_project_sort")]
pub project_sort: String,
#[serde(default)]
pub project_sort_desc: bool,
/// Visible Project panel columns after Name and Label, in order (`type`, `size`, `fps`,
/// `duration`, `path`, `comment`); toggled from the column header's context menu.
#[serde(default = "default_project_columns")]
pub project_columns: Vec<String>,
/// Horizontal scroll of the Project panel's optional columns (Name and Label stay put).
#[serde(default)]
pub project_hscroll: f32,
/// Width of the Project panel's Name column set by dragging its edge in the header (points);
/// `None` fits the longest name shown (double-clicking the edge goes back to that).
#[serde(default)]
pub project_name_w: Option<f32>,
/// Vertical scroll of the Project panel's item list (points); only visible rows are drawn.
#[serde(default)]
pub project_scroll: f32,
/// Effect Controls twirl state (group uids that are collapsed).
pub fx_closed: BTreeSet<u64>,
/// Slider params whose slider row is twirled open (AE hides sliders by default).
#[serde(default)]
pub fx_slider_open: BTreeSet<u64>,
/// Effect Controls: a point crosshair or colour eyedropper waiting for a viewer click.
#[serde(default)]
pub fx_pick: Option<FxPick>,
/// Character panel colour eyedropper waiting for a viewer click.
#[serde(default)]
pub text_color_pick: Option<TextColorPick>,
/// Curves editor: shown channel (index into RGB/Red/Green/Blue/Alpha) per effect uid.
#[serde(default)]
pub fx_curve_channel: BTreeMap<u64, usize>,
/// Curves editors in pencil (freehand) mode, by effect uid.
#[serde(default)]
pub fx_curve_pencil: BTreeSet<u64>,
/// Levels (Individual Controls) editor: shown channel per effect uid.
#[serde(default)]
pub fx_levels_channel: BTreeMap<u64, usize>,
/// Effects & Presets: recently applied effect ids, most recent first.
#[serde(default)]
pub effects_recent: Vec<String>,
/// Effects & Presets contents-menu view options.
#[serde(default)]
pub effects_view: EffectsView,
pub snapping: bool,
pub start_screen: bool,
/// The Home screen shows its Learn tab (tutorials) instead of the recent projects.
#[serde(default)]
pub home_learn: bool,
/// The Home screen shows its Templates tab (New from Template gallery).
#[serde(default)]
pub home_templates: bool,
/// Align panel: Align Layers to Selection (off: to the composition).
#[serde(default)]
pub align_to_selection: bool,
/// Composition ▸ Preview ▸ Cache Frames When Idle.
pub cache_when_idle: bool,
/// Tracker panel ▸ Motion Source chosen without a tracker yet (layer id).
#[serde(default)]
pub tracker_source: Option<u64>,
/// Layer shown in the Layer panel (None = the first selected layer).
#[serde(default)]
pub layer_panel: Option<u64>,
/// Layer panel View: number of effects rendered (None = through the last Paint effect).
#[serde(default)]
pub layer_view: Option<usize>,
/// Layer panel magnification in points per layer pixel (None = fit), set by its
/// magnification menu, View ▸ Zoom In / Zoom Out while it is focused, the mouse wheel and the
/// Zoom tool.
#[serde(default)]
pub layer_zoom: Option<f32>,
/// Layer panel pan offset in points from the centred position (Hand tool, Space-drag).
#[serde(default)]
pub layer_pan: [f32; 2],
/// Wiggler / Smoother / Motion Sketch settings.
#[serde(default)]
pub anim_tools: AnimToolsState,
/// Composition Mini-Flowchart popup position (open when set).
#[serde(default)]
pub mini_flowchart: Option<[f32; 2]>,
/// Flowchart panel options (layers, effects, solids, direction, root comp).
#[serde(default)]
pub flowchart: crate::panels::flowchart::FlowOptions,
/// Video Scopes panel options.
#[serde(default)]
pub scopes: crate::panels::scopes_panel::ScopesState,
}
impl Default for UiState {
fn default() -> Self {
UiState {
tool: Tool::Selection,
script_console: Default::default(),
slot_tools: Tool::SLOTS.iter().map(|s| s[0]).collect(),
workspace: "Default".into(),
dock: crate::dock::Workspace::new(crate::dock::workspace("Default")),
saved_workspaces: Default::default(),
native_outer: Vec::new(),
maximized: None,
locked_tabs: BTreeSet::new(),
viewers: Default::default(),
active_viewer: 0,
focused: PanelKind::Composition,
viewer: ViewerState::default(),
timeline: TimelineState::default(),
theme: Default::default(),
show_menu_bar: true,
status: String::new(),
effects_search: String::new(),
effects_open: BTreeSet::new(),
project_search: String::new(),
project_status: ProjectStatus::All,
project_open_folders: BTreeSet::new(),
project_sort: default_project_sort(),
project_sort_desc: false,
project_hscroll: 0.0,
project_name_w: None,
project_scroll: 0.0,
project_columns: default_project_columns(),
fx_closed: BTreeSet::new(),
fx_slider_open: BTreeSet::new(),
fx_pick: None,
text_color_pick: None,
fx_curve_channel: BTreeMap::new(),
fx_curve_pencil: BTreeSet::new(),
fx_levels_channel: BTreeMap::new(),
effects_recent: Vec::new(),
effects_view: EffectsView::default(),
snapping: true,
start_screen: false,
home_learn: false,
home_templates: false,
align_to_selection: false,
cache_when_idle: true,
tracker_source: None,
layer_panel: None,
layer_view: None,
layer_zoom: None,
layer_pan: [0.0, 0.0],
anim_tools: AnimToolsState::default(),
mini_flowchart: None,
flowchart: Default::default(),
scopes: Default::default(),
}
}
}
/// What a `ui.screenshot` captures.
///
/// A panel or element is an identity, not a rectangle: it is resolved again when the capture is
/// issued, after the settle passes, so a panel that moved or resized while the UI settled is
/// captured where it is now (never with the rectangle from request admission).
#[derive(Clone, Debug, PartialEq)]
pub enum ShotTarget {
/// The whole root surface.
Window,
/// A fixed rectangle in logical points, relative to the root surface's top-left corner.
Rect([f32; 4]),
/// One dock panel's automation rectangle (`panel.<id>`).
Panel(PanelKind),
/// One automation element id's rectangle.
Id(String),
}
/// The comp frames a playback pass covers (`first..=last`).
#[derive(Clone, Copy, Debug)]
pub(crate) struct FrameWindow {
pub(crate) first: i64,
pub(crate) last: i64,
}
/// The inputs of one playback pass: the viewer series it renders, the preview plan and
/// position, and the frame range and clock settings its steps share. `queued` is a snapshot of
/// the in-flight render count that each step copies. Built once in `advance_playback`.
#[derive(Clone, Copy)]
pub(crate) struct PlaybackPass<'a> {
pub(crate) ctx: &'a egui::Context,
pub(crate) comp: &'a effectcraft_engine::project::Comp,
pub(crate) cid: ItemId,
pub(crate) scale: f64,
pub(crate) now: f64,
pub(crate) frame_rate: effectcraft_engine::time::FrameRate,
pub(crate) series: FrameKey,
pub(crate) plan: Option<effectcraft_engine::preview::PreviewPlan>,
pub(crate) window: FrameWindow,
pub(crate) cur: i64,
pub(crate) video: bool,
pub(crate) looping: bool,
pub(crate) ahead: i64,
pub(crate) queued: usize,
}