mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-01 14:08:38 -06:00
Merge remote-tracking branch 'origin/main' into HEAD
This commit is contained in:
@@ -28,6 +28,7 @@ pub use choices::{APPEARANCE_MODES, DARK_THEMES, LANGUAGES, LIGHT_THEMES, THEMES
|
||||
pub use dialog::{Item, Kind, Page, pages, pages_json, todo_keys};
|
||||
pub use document::{migrate, page_id, section_key};
|
||||
pub use queries::{CacheBudgets, WEB_LAYER_CACHE, WEB_MEDIA_CACHE, mask_color};
|
||||
pub use recent::{MAX_RECENT_SCRIPTS, RecentScript};
|
||||
pub use sections::{
|
||||
Appearance, Audio, AutoSave, CompPreset, CompositionPrefs, Disk, Export, FacePrefs, General, Grids, Import, LabelPref, Memory, Previews, ProjectPrefs,
|
||||
RotoPrefs, Scripting, Startup, ThreeD, TypePrefs, Video,
|
||||
@@ -35,7 +36,7 @@ pub use sections::{
|
||||
use sections::{Extra, default_labels, lenient_comp_presets};
|
||||
|
||||
/// Current settings layout version.
|
||||
pub const PREFS_VERSION: u32 = 2;
|
||||
pub const PREFS_VERSION: u32 = 3;
|
||||
/// Settings file name in the config store.
|
||||
pub const PREFS_FILE: &str = "prefs.json";
|
||||
|
||||
@@ -74,6 +75,8 @@ pub struct Prefs {
|
||||
/// Animation ▸ Recent Animation Presets (`.ecpreset` paths or built-in preset ids), newest
|
||||
/// first.
|
||||
pub recent_presets: Vec<String>,
|
||||
/// File ▸ Scripts ▸ Recent Script Files, newest first, at most [`MAX_RECENT_SCRIPTS`].
|
||||
pub recent_scripts: Vec<RecentScript>,
|
||||
/// Character panel: recently used font families, newest first.
|
||||
pub recent_fonts: Vec<String>,
|
||||
/// Effects & Presets: the starred effects' ids, in the order starred.
|
||||
@@ -117,6 +120,7 @@ impl Default for Prefs {
|
||||
recent_projects: vec![],
|
||||
recent_footage: vec![],
|
||||
recent_presets: vec![],
|
||||
recent_scripts: vec![],
|
||||
recent_fonts: vec![],
|
||||
effects_favorites: vec![],
|
||||
preview: crate::preview::PreviewSettings::default(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::choices::{APPEARANCE_MODES, DARK_THEMES, LANGUAGES, LIGHT_THEMES, WINDOW_GRAPHICS};
|
||||
use super::*;
|
||||
use super::{Value, json};
|
||||
|
||||
// ---------------------------------------------------------------- schema (Settings dialog)
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use super::choices::{APPEARANCE_MODES, DARK_THEMES, LANGUAGES, LIGHT_THEMES, THEMES, WINDOW_GRAPHICS, choices, is_choice, theme_family};
|
||||
use super::recent::MAX_EFFECTS_FAVORITES;
|
||||
use super::recent::{MAX_EFFECTS_FAVORITES, MAX_RECENT_SCRIPTS};
|
||||
use super::sections::{CompPreset, default_labels, parse_hex};
|
||||
use super::*;
|
||||
use super::{Appearance, General, Map, PREFS_VERSION, Prefs, Startup, Value, json};
|
||||
|
||||
/// Bring an older settings document up to [`PREFS_VERSION`].
|
||||
///
|
||||
/// - version 0/1 (early builds) kept a flat object: `undoLevels`, `autoSaveEnabled`,
|
||||
/// `autoSaveMinutes`, `autoSaveVersions`, `labelNames` (array) and `theme` at the top level.
|
||||
/// - version 2 defaulted the disk cache to 100 GB, more than most laptops have free; the stored
|
||||
/// old default becomes the new one (a size the user chose is kept).
|
||||
pub fn migrate(mut v: Value) -> Value {
|
||||
let Some(obj) = v.as_object_mut() else { return json!({}) };
|
||||
let version = obj.get("version").and_then(Value::as_u64).unwrap_or(0);
|
||||
@@ -34,6 +36,12 @@ pub fn migrate(mut v: Value) -> Value {
|
||||
obj.insert("labels".into(), serde_json::to_value(labels).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
if version < 3
|
||||
&& let Some(d) = obj.get_mut("disk").and_then(Value::as_object_mut)
|
||||
&& d.get("diskCacheMaxGb").and_then(Value::as_u64) == Some(100)
|
||||
{
|
||||
d.insert("diskCacheMaxGb".into(), json!(20));
|
||||
}
|
||||
// Settings saved before appearance modes had one theme: keep showing it, as a fixed Dark or
|
||||
// Light mode, instead of switching established users to Auto.
|
||||
if let Some(a) = obj.get_mut("appearance").and_then(Value::as_object_mut)
|
||||
@@ -138,6 +146,7 @@ impl Prefs {
|
||||
self.grids.grid_spacing = self.grids.grid_spacing.clamp(1.0, 10_000.0);
|
||||
let n = self.general.recent_items as usize;
|
||||
self.recent_projects.truncate(n);
|
||||
self.recent_scripts.truncate(MAX_RECENT_SCRIPTS);
|
||||
}
|
||||
|
||||
/// The value at a dotted key (`general.undoLevels`, `labels.3.color`); `""` = everything.
|
||||
@@ -203,6 +212,7 @@ impl Prefs {
|
||||
recent_projects: std::mem::take(&mut self.recent_projects),
|
||||
recent_footage: std::mem::take(&mut self.recent_footage),
|
||||
recent_presets: std::mem::take(&mut self.recent_presets),
|
||||
recent_scripts: std::mem::take(&mut self.recent_scripts),
|
||||
recent_fonts: std::mem::take(&mut self.recent_fonts),
|
||||
effects_favorites: std::mem::take(&mut self.effects_favorites),
|
||||
preview: std::mem::take(&mut self.preview),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::sections::parse_hex;
|
||||
use super::*;
|
||||
use super::{Label, Prefs, Serialize, Tick};
|
||||
|
||||
impl Prefs {
|
||||
/// Display name of a label (Labels settings).
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Prefs;
|
||||
|
||||
impl Prefs {
|
||||
/// Remember a project in File ▸ Open Recent.
|
||||
@@ -45,6 +47,34 @@ impl Prefs {
|
||||
pub fn push_recent_preset(&mut self, path: &str) {
|
||||
push_mru(&mut self.recent_presets, path, self.general.recent_items as usize);
|
||||
}
|
||||
|
||||
/// Remember a script that ran for File ▸ Scripts ▸ Recent Script Files.
|
||||
pub fn push_recent_script(&mut self, script: RecentScript) {
|
||||
self.recent_scripts.retain(|x| *x != script);
|
||||
self.recent_scripts.insert(0, script);
|
||||
self.recent_scripts.truncate(MAX_RECENT_SCRIPTS);
|
||||
}
|
||||
}
|
||||
|
||||
/// Most scripts File ▸ Scripts ▸ Recent Script Files lists.
|
||||
pub const MAX_RECENT_SCRIPTS: usize = 10;
|
||||
|
||||
/// A File ▸ Scripts ▸ Recent Script Files entry, stored as the `file.runScript` parameter that
|
||||
/// ran it: `{"path": …}` for a script file, `{"name": …}` for an installed or bundled script.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RecentScript {
|
||||
Path(String),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
impl RecentScript {
|
||||
/// The file's path or the script's name.
|
||||
pub fn text(&self) -> &str {
|
||||
match self {
|
||||
RecentScript::Path(s) | RecentScript::Name(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Most effects Effects & Presets keeps starred (a hand-edited settings file can hold more).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::choices::theme_family;
|
||||
use super::*;
|
||||
use super::{BTreeMap, Deserialize, Label, Serialize, Value};
|
||||
|
||||
/// Unknown keys inside a page, kept so a newer version's settings survive a round trip.
|
||||
pub(super) type Extra = BTreeMap<String, Value>;
|
||||
@@ -179,7 +179,7 @@ page!(Audio {
|
||||
|
||||
page!(Disk {
|
||||
disk_cache_enabled: bool = true,
|
||||
disk_cache_max_gb: u32 = 100,
|
||||
disk_cache_max_gb: u32 = 20,
|
||||
disk_cache_folder: String = String::new(),
|
||||
media_cache_folder: String = String::new(),
|
||||
conformed_media_folder: String = String::new(),
|
||||
|
||||
@@ -82,7 +82,9 @@ impl Session {
|
||||
/// Take the latest reading of the system's memory and start the next one in the background
|
||||
/// (desktop frontends call this every few seconds; it never waits for the system), then
|
||||
/// re-apply the cache budgets when the reading changes them. Returns the effective budgets.
|
||||
/// The disk cache's free disk space is read the same way (it keeps a reserve free).
|
||||
pub fn memory_tick(&mut self) -> crate::prefs::CacheBudgets {
|
||||
self.poll_disk_space();
|
||||
let before = self.prefs.cache_budgets(self.sys_memory);
|
||||
self.sys_memory = self.memory_watch.latest().or(self.sys_memory);
|
||||
self.memory_watch.poll();
|
||||
@@ -98,6 +100,19 @@ impl Session {
|
||||
after
|
||||
}
|
||||
|
||||
/// Read the disk cache's free disk space in the background and apply it there (the reserve
|
||||
/// it keeps free): the free space and the cache's size are taken at the same moment, and the
|
||||
/// reader holds its own cache, so a reading never reaches another folder's.
|
||||
fn poll_disk_space(&mut self) {
|
||||
if let Some(dc) = &self.disk_cache {
|
||||
let dc = dc.clone();
|
||||
self.disk_watch.poll_with("ec-disk-space", move || {
|
||||
dc.set_disk_space(crate::sysinfo::disk_space(dc.folder()));
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply Settings ▸ Media & Disk Cache ▸ Disk Cache: open (or close) the persistent cache
|
||||
/// and attach it to the layer cache. Without a folder setting the platform cache folder is
|
||||
/// used, but only by frontends with a settings store (headless sessions stay off disk).
|
||||
@@ -113,6 +128,7 @@ impl Session {
|
||||
// (no file system in the browser: the disk cache stays off there)
|
||||
let Some(folder) = folder.filter(|_| d.disk_cache_enabled && !cfg!(target_arch = "wasm32")) else {
|
||||
self.disk_cache = None;
|
||||
self.disk_watch = Default::default();
|
||||
self.layer_cache.set_disk(None);
|
||||
return;
|
||||
};
|
||||
@@ -126,17 +142,25 @@ impl Session {
|
||||
c.set_max_bytes(max);
|
||||
c.clone()
|
||||
}
|
||||
// A new folder: the free-space reading was for the old one (a reader still running
|
||||
// holds the old watcher's state, so it can't write into the new one).
|
||||
_ => match DiskCache::open(&folder, max) {
|
||||
Ok(c) => c,
|
||||
Ok(c) => {
|
||||
self.disk_watch = Default::default();
|
||||
c
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("disk cache at {}: {e}", folder.display());
|
||||
self.disk_cache = None;
|
||||
self.disk_watch = Default::default();
|
||||
self.layer_cache.set_disk(None);
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
self.disk_cache = Some(dc.clone());
|
||||
// The reserve applies from the start (a new folder's watcher is idle).
|
||||
self.poll_disk_space();
|
||||
let salt = self.refresh_disk_salt(true).unwrap_or_default();
|
||||
self.layer_cache.set_disk(Some((dc.clone(), salt)));
|
||||
// Roto Brush / Object Matte segmentations persist there too (held weakly: switching the
|
||||
|
||||
@@ -105,6 +105,21 @@ pub fn bindables(ui: &[UiCommand]) -> Vec<Bindable> {
|
||||
&mut out,
|
||||
);
|
||||
}
|
||||
// File ▸ Scripts ▸ Recent Script Files lists its scripts at run time; the first one, the most
|
||||
// recent, has a key.
|
||||
let recent = json!({"recent": 0});
|
||||
push(
|
||||
Bindable {
|
||||
key: binding_key("file.runScript", &recent),
|
||||
command: "file.runScript".into(),
|
||||
params: recent,
|
||||
label: "Run Most Recent Script File".into(),
|
||||
path: vec!["File".into(), "Scripts".into(), "Recent Script Files".into()],
|
||||
scope: APP_SCOPE.into(),
|
||||
defaults: normalize("Cmd+Alt+Shift+D").into_iter().collect(),
|
||||
},
|
||||
&mut out,
|
||||
);
|
||||
for c in crate::command_specs() {
|
||||
if !c.journal || entries.iter().any(|(_, e)| e.command == c.id) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user