app: about dialog, modal close affordances, more preferences
- Help > About Oak… opens a real dialog (was a placeholder print): name + version, the GPL-3.0 line, the Olive fork notice and the thanks to Enzo GD (Community Promoter). - Modal (gpui_widgets, submodule bump): a title-row close button on every dialog and opt-in backdrop dismissal (dismiss_on_mask) — Help > Search Actions and About use it, so they close on an outside click (previously a buttonless modal could only be closed with Escape). - Preferences general tab grows two live settings: the cache-ahead pre-render window (PlaybackPreRenderFrames, consumed by the preview scheduler every playback tick) and the library storage backend (Storage/Backend sqlite/pg + Storage/PgUrl connection string, read by oak-storage's write-through when a project binds; the pg URL field shows only for PostgreSQL and commits on dialog close). The C++ auto-recovery toggle is skipped on purpose: the Rust snapshot loop has no disable semantic, so the switch would be dead. i18n keys for all eight packs; new tests cover the about modal and the preferences writes.
This commit is contained in:
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "Standardort verwenden"
|
||||
"projprops.cache.alongside": "Zusammen mit dem Projekt speichern"
|
||||
"projprops.cache.custom": "Benutzerdefinierten Speicherort verwenden:"
|
||||
"about.title": "Über Oak"
|
||||
"about.description": "Oak Video Editor ist ein freier, quelloffener nichtlinearer Videoeditor. Diese Software ist unter der GNU GPL Version 3 lizenziert."
|
||||
"about.thanks": "Besonderer Dank gilt Enzo GD, unserem Community Promoter, für seine großzügige Unterstützung bei der Verbreitung dieses Projekts in dessen Anfangszeit."
|
||||
"about.fork_notice": "Dieses Projekt ist ein Fork des Olive Video Editors."
|
||||
"file.open.title": "Projekt öffnen"
|
||||
"file.import_footage.title": "Material importieren"
|
||||
"preferences.title": "Einstellungen"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "Verzeichnis für den Platten-Cache"
|
||||
"preferences.cache.browse": "Durchsuchen…"
|
||||
"preferences.cache.ahead": "Vorauspuffer (Frames)"
|
||||
"preferences.section.storage": "Speicher"
|
||||
"preferences.storage.backend": "Backend des Projektarchivs"
|
||||
"preferences.storage.pg_url": "PostgreSQL-Verbindungszeichenfolge"
|
||||
"preferences.storage.restart_hint": "Änderungen am Speicher-Backend gelten für Projekte, die anschließend geöffnet werden (ein Neustart wird empfohlen)."
|
||||
"preferences.proxy.enable": "Proxy-Medien verwenden"
|
||||
"preferences.proxy.resolution": "Proxy-Auflösung"
|
||||
"preferences.proxy.full": "Volle Auflösung"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "Use Default Location"
|
||||
"projprops.cache.alongside": "Store Alongside Project"
|
||||
"projprops.cache.custom": "Use Custom Location:"
|
||||
"about.title": "About Oak"
|
||||
"about.description": "Oak Video Editor is a free open source non-linear video editor. This software is licensed under the GNU GPL Version 3."
|
||||
"about.thanks": "Special thanks to Enzo GD, our Community Promoter, for his generous support in spreading the word about this project in its early days."
|
||||
"about.fork_notice": "This project is a fork of Olive Video Editor."
|
||||
"file.open.title": "Open Project"
|
||||
"file.import_footage.title": "Import Footage"
|
||||
"preferences.title": "Preferences"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "Disk cache directory"
|
||||
"preferences.cache.browse": "Browse…"
|
||||
"preferences.cache.ahead": "Cache ahead (frames)"
|
||||
"preferences.section.storage": "Storage"
|
||||
"preferences.storage.backend": "Library storage backend"
|
||||
"preferences.storage.pg_url": "PostgreSQL connection string"
|
||||
"preferences.storage.restart_hint": "Storage backend changes apply to projects opened afterwards (a restart is recommended)."
|
||||
"preferences.proxy.enable": "Use proxy media"
|
||||
"preferences.proxy.resolution": "Proxy resolution"
|
||||
"preferences.proxy.full": "Full resolution"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "Usar ubicación predeterminada"
|
||||
"projprops.cache.alongside": "Almacenar junto al proyecto"
|
||||
"projprops.cache.custom": "Usar ubicación personalizada:"
|
||||
"about.title": "Acerca de Oak"
|
||||
"about.description": "Oak Video Editor es un editor de vídeo no lineal libre y de código abierto. Este software está licenciado bajo la GNU GPL versión 3."
|
||||
"about.thanks": "Un agradecimiento especial a Enzo GD, nuestro Community Promoter, por su generoso apoyo al difundir este proyecto en sus primeros días."
|
||||
"about.fork_notice": "Este proyecto es un fork de Olive Video Editor."
|
||||
"file.open.title": "Abrir proyecto"
|
||||
"file.import_footage.title": "Importar material"
|
||||
"preferences.title": "Preferencias"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "Directorio de caché en disco"
|
||||
"preferences.cache.browse": "Examinar…"
|
||||
"preferences.cache.ahead": "Búfer anticipado (fotogramas)"
|
||||
"preferences.section.storage": "Almacenamiento"
|
||||
"preferences.storage.backend": "Backend de almacenamiento de la biblioteca"
|
||||
"preferences.storage.pg_url": "Cadena de conexión de PostgreSQL"
|
||||
"preferences.storage.restart_hint": "Los cambios del backend de almacenamiento se aplican a los proyectos abiertos después del cambio (se recomienda reiniciar)."
|
||||
"preferences.proxy.enable": "Usar medios proxy"
|
||||
"preferences.proxy.resolution": "Resolución del proxy"
|
||||
"preferences.proxy.full": "Resolución completa"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "Utiliser l'emplacement par défaut"
|
||||
"projprops.cache.alongside": "Stocker à côté du projet"
|
||||
"projprops.cache.custom": "Utiliser un emplacement personnalisé :"
|
||||
"about.title": "À propos d'Oak"
|
||||
"about.description": "Oak Video Editor est un éditeur vidéo non linéaire libre et open source. Ce logiciel est sous licence GNU GPL version 3."
|
||||
"about.thanks": "Remerciements particuliers à Enzo GD, notre Community Promoter, pour son généreux soutien à la diffusion de ce projet à ses débuts."
|
||||
"about.fork_notice": "Ce projet est un fork d'Olive Video Editor."
|
||||
"file.open.title": "Ouvrir le projet"
|
||||
"file.import_footage.title": "Importer des médias"
|
||||
"preferences.title": "Préférences"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "Dossier du cache disque"
|
||||
"preferences.cache.browse": "Parcourir…"
|
||||
"preferences.cache.ahead": "Préchargement (images)"
|
||||
"preferences.section.storage": "Stockage"
|
||||
"preferences.storage.backend": "Backend de stockage de la bibliothèque"
|
||||
"preferences.storage.pg_url": "Chaîne de connexion PostgreSQL"
|
||||
"preferences.storage.restart_hint": "Les changements de backend de stockage s'appliquent aux projets ouverts après la modification (un redémarrage est recommandé)."
|
||||
"preferences.proxy.enable": "Utiliser les médias proxy"
|
||||
"preferences.proxy.resolution": "Résolution du proxy"
|
||||
"preferences.proxy.full": "Pleine résolution"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "デフォルトの場所を使用"
|
||||
"projprops.cache.alongside": "プロジェクトと同じ場所に保存"
|
||||
"projprops.cache.custom": "カスタムの場所を使用:"
|
||||
"about.title": "Oak について"
|
||||
"about.description": "Oak Video Editor はフリーでオープンソースのノンリニア動画エディタです。本ソフトウェアは GNU GPL バージョン 3 でライセンスされています。"
|
||||
"about.thanks": "プロジェクト初期に広く紹介してくれたコミュニティプロモーター Enzo GD に心より感謝します。"
|
||||
"about.fork_notice": "本プロジェクトは Olive Video Editor のフォークです。"
|
||||
"file.open.title": "プロジェクトを開く"
|
||||
"file.import_footage.title": "素材を読み込み"
|
||||
"preferences.title": "環境設定"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "ディスクキャッシュの保存先"
|
||||
"preferences.cache.browse": "参照…"
|
||||
"preferences.cache.ahead": "先読みキャッシュ(フレーム)"
|
||||
"preferences.section.storage": "ストレージ"
|
||||
"preferences.storage.backend": "ライブラリのストレージバックエンド"
|
||||
"preferences.storage.pg_url": "PostgreSQL接続文字列"
|
||||
"preferences.storage.restart_hint": "ストレージバックエンドの変更は、変更後に開いたプロジェクトに適用されます(再起動を推奨)。"
|
||||
"preferences.proxy.enable": "プロキシメディアを使用"
|
||||
"preferences.proxy.resolution": "プロキシ解像度"
|
||||
"preferences.proxy.full": "フル解像度"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "Usar localização padrão"
|
||||
"projprops.cache.alongside": "Armazenar junto ao projeto"
|
||||
"projprops.cache.custom": "Usar localização personalizada:"
|
||||
"about.title": "Sobre o Oak"
|
||||
"about.description": "O Oak Video Editor é um editor de vídeo não linear livre e de código aberto. Este software é licenciado sob a GNU GPL versão 3."
|
||||
"about.thanks": "Um agradecimento especial a Enzo GD, nosso Community Promoter, por seu generoso apoio na divulgação deste projeto em seus primórdios."
|
||||
"about.fork_notice": "Este projeto é um fork do Olive Video Editor."
|
||||
"file.open.title": "Abrir projeto"
|
||||
"file.import_footage.title": "Importar mídia"
|
||||
"preferences.title": "Preferências"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "Diretório do cache em disco"
|
||||
"preferences.cache.browse": "Procurar…"
|
||||
"preferences.cache.ahead": "Pré-busca (quadros)"
|
||||
"preferences.section.storage": "Armazenamento"
|
||||
"preferences.storage.backend": "Backend de armazenamento da biblioteca"
|
||||
"preferences.storage.pg_url": "String de conexão do PostgreSQL"
|
||||
"preferences.storage.restart_hint": "As alterações do backend de armazenamento valem para projetos abertos depois da mudança (é recomendável reiniciar)."
|
||||
"preferences.proxy.enable": "Usar mídia proxy"
|
||||
"preferences.proxy.resolution": "Resolução do proxy"
|
||||
"preferences.proxy.full": "Resolução completa"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "Использовать расположение по умолчанию"
|
||||
"projprops.cache.alongside": "Хранить рядом с проектом"
|
||||
"projprops.cache.custom": "Использовать пользовательское расположение:"
|
||||
"about.title": "О программе Oak"
|
||||
"about.description": "Oak Video Editor — свободный нелинейный видеоредактор с открытым исходным кодом. Программа распространяется по лицензии GNU GPL версии 3."
|
||||
"about.thanks": "Особая благодарность Enzo GD, нашему Community Promoter, за щедрую поддержку в распространении этого проекта в первые дни."
|
||||
"about.fork_notice": "Этот проект является форком Olive Video Editor."
|
||||
"file.open.title": "Открыть проект"
|
||||
"file.import_footage.title": "Импорт медиафайлов"
|
||||
"preferences.title": "Настройки"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "Каталог дискового кэша"
|
||||
"preferences.cache.browse": "Обзор…"
|
||||
"preferences.cache.ahead": "Упреждающий кэш (кадры)"
|
||||
"preferences.section.storage": "Хранилище"
|
||||
"preferences.storage.backend": "Бэкенд хранилища библиотеки"
|
||||
"preferences.storage.pg_url": "Строка подключения PostgreSQL"
|
||||
"preferences.storage.restart_hint": "Изменения бэкенда хранилища применяются к проектам, открытым после изменения (рекомендуется перезапуск)."
|
||||
"preferences.proxy.enable": "Использовать прокси-медиа"
|
||||
"preferences.proxy.resolution": "Разрешение прокси"
|
||||
"preferences.proxy.full": "Полное разрешение"
|
||||
|
||||
@@ -247,6 +247,10 @@
|
||||
"projprops.cache.default": "使用默认位置"
|
||||
"projprops.cache.alongside": "随工程文件存储"
|
||||
"projprops.cache.custom": "使用自定义位置:"
|
||||
"about.title": "关于 Oak"
|
||||
"about.description": "Oak 视频编辑器是一款自由开源的非线性视频编辑器。本软件以 GNU GPL 第三版许可证发布。"
|
||||
"about.thanks": "特别感谢我们的社区推广者 Enzo GD,感谢他在项目早期为传播本项目所给予的慷慨支持。"
|
||||
"about.fork_notice": "本项目是 Olive 视频编辑器的 fork。"
|
||||
"file.open.title": "打开项目"
|
||||
"file.import_footage.title": "导入素材"
|
||||
"preferences.title": "偏好设置"
|
||||
@@ -271,6 +275,11 @@
|
||||
"preferences.theme.light": "Olive Light"
|
||||
"preferences.cache.dir": "磁盘缓存目录"
|
||||
"preferences.cache.browse": "浏览…"
|
||||
"preferences.cache.ahead": "缓存提前量(帧)"
|
||||
"preferences.section.storage": "存储"
|
||||
"preferences.storage.backend": "素材库存储后端"
|
||||
"preferences.storage.pg_url": "PostgreSQL 连接串"
|
||||
"preferences.storage.restart_hint": "存储后端更改在之后打开的项目上生效(建议重启)。"
|
||||
"preferences.proxy.enable": "使用代理媒体"
|
||||
"preferences.proxy.resolution": "代理分辨率"
|
||||
"preferences.proxy.full": "原始分辨率"
|
||||
|
||||
+133
-2
@@ -121,6 +121,8 @@ mod modal_ids {
|
||||
pub const PLUGIN_PROGRESS: usize = 10;
|
||||
/// The action search dialog (Help > Search Actions…, the `/` key).
|
||||
pub const ACTION_SEARCH: usize = 11;
|
||||
/// The about dialog (Help > About Oak…).
|
||||
pub const ABOUT: usize = 13;
|
||||
/// The project properties dialog (File > Project Properties…).
|
||||
pub const PROJECT_PROPERTIES: usize = 12;
|
||||
}
|
||||
@@ -187,6 +189,8 @@ enum ModalState<E: AppEngine> {
|
||||
modal: Entity<Modal>,
|
||||
content: Entity<crate::dialogs::ProjectPropertiesContent<E>>,
|
||||
},
|
||||
/// The about dialog (Help > About Oak…; static content).
|
||||
About { modal: Entity<Modal> },
|
||||
}
|
||||
|
||||
/// A running export: the session the tick loop drains for progress.
|
||||
@@ -207,7 +211,8 @@ impl<E: AppEngine> ModalState<E> {
|
||||
| ModalState::ManagerDelete { modal, .. }
|
||||
| ModalState::Proxy { modal, .. }
|
||||
| ModalState::ActionSearch { modal, .. }
|
||||
| ModalState::ProjectProperties { modal, .. } => Some(modal.clone()),
|
||||
| ModalState::ProjectProperties { modal, .. }
|
||||
| ModalState::About { modal } => Some(modal.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1080,6 +1085,7 @@ impl<E: AppEngine> OakApp<E> {
|
||||
| A::MulticamSwitchNoSplit9 => {}
|
||||
// --- Help --------------------------------------------------------
|
||||
A::ActionSearch => self.open_action_search(cx),
|
||||
A::About => self.open_about(cx),
|
||||
// --- everything else is a placeholder --------------------------
|
||||
other => println!(
|
||||
"[action] {} not wired yet (placeholder)",
|
||||
@@ -1927,7 +1933,10 @@ impl<E: AppEngine> OakApp<E> {
|
||||
let modal = app.new(|cx| {
|
||||
Modal::new(
|
||||
modal_ids::ACTION_SEARCH,
|
||||
ModalOptions::new(crate::i18n::tr("menu.help.action_search"), px(640.0)),
|
||||
ModalOptions::new(crate::i18n::tr("menu.help.action_search"), px(640.0))
|
||||
// Launcher-style: a backdrop click dismisses (same as
|
||||
// Escape and the title-row ✕).
|
||||
.with_dismiss_on_mask(true),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -1959,6 +1968,30 @@ impl<E: AppEngine> OakApp<E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the about dialog (Help > About Oak…): the app name, version,
|
||||
/// license line and fork notice. No engine interaction.
|
||||
fn open_about(&mut self, cx: &mut Context<Self>) {
|
||||
if !matches!(self.modal, ModalState::None) {
|
||||
return;
|
||||
}
|
||||
self.spawn_modal(cx, |window, app| {
|
||||
let content = app.new(|_cx| crate::dialogs::AboutContent::new());
|
||||
let modal = app.new(|cx| {
|
||||
Modal::new(
|
||||
modal_ids::ABOUT,
|
||||
ModalOptions::new(crate::i18n::tr("about.title"), px(480.0))
|
||||
.with_button(DialogButton::primary(crate::i18n::tr("dialog.ok")))
|
||||
// Launcher-style dismissal is natural here too.
|
||||
.with_dismiss_on_mask(true),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.with_content(content.clone())
|
||||
});
|
||||
ModalState::About { modal }
|
||||
});
|
||||
}
|
||||
|
||||
/// Opens the proxy settings dialog (Tools > Proxy Settings; the C++
|
||||
/// `ProxyDialog`): the global generation settings plus the footage
|
||||
/// proxy list with Generate / Delete buttons.
|
||||
@@ -2225,6 +2258,7 @@ impl<E: AppEngine> OakApp<E> {
|
||||
}
|
||||
}
|
||||
}
|
||||
modal_ids::ABOUT => self.close_modal(cx),
|
||||
modal_ids::PROJECT_PROPERTIES => {
|
||||
if let ModalState::ProjectProperties { content, .. } = &self.modal {
|
||||
let content = content.clone();
|
||||
@@ -3158,6 +3192,103 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The General tab's new rows are live end to end: the cache-ahead spin
|
||||
/// box writes `PlaybackPreRenderFrames` (the playback pre-render window
|
||||
/// reads it every tick) and the storage backend combo writes
|
||||
/// `Storage/Backend` — PostgreSQL reveals the `Storage/PgUrl` field the
|
||||
/// write-through library consumes.
|
||||
#[gpui::test]
|
||||
async fn preferences_general_tab_writes_cache_ahead_and_storage(cx: &mut TestAppContext) {
|
||||
let _guard = crate::actions::shortcuts_test_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let _lang = crate::i18n::lang_test_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
crate::i18n::set_language_code("en-US");
|
||||
|
||||
// The config store is process-wide: pin the keys to a known state and
|
||||
// restore them afterwards so no other test inherits these writes.
|
||||
struct Restore(&'static str, String);
|
||||
impl Drop for Restore {
|
||||
fn drop(&mut self) {
|
||||
crate::oakui::real::config_set_string(self.0, &self.1);
|
||||
}
|
||||
}
|
||||
let _ahead = Restore(
|
||||
"PlaybackPreRenderFrames",
|
||||
crate::oakui::real::config_get_string("PlaybackPreRenderFrames"),
|
||||
);
|
||||
let _backend = Restore(
|
||||
"Storage/Backend",
|
||||
crate::oakui::real::config_get_string("Storage/Backend"),
|
||||
);
|
||||
let _pg_url = Restore(
|
||||
"Storage/PgUrl",
|
||||
crate::oakui::real::config_get_string("Storage/PgUrl"),
|
||||
);
|
||||
crate::oakui::real::config_set_int("PlaybackPreRenderFrames", 120);
|
||||
crate::oakui::real::config_set_string("Storage/Backend", "sqlite");
|
||||
|
||||
let (window, root) = mock_shell(cx);
|
||||
cx.update(|app| root.update(app, |app, cx| app.on_menu(menu_ids::PREFERENCES, cx)));
|
||||
cx.run_until_parked();
|
||||
cx.update_window(window.into(), |_root, window, cx| {
|
||||
window.draw(cx).clear();
|
||||
})
|
||||
.expect("window is still open");
|
||||
cx.run_until_parked();
|
||||
// Clear the app keymap: the global up/down/home/end bindings
|
||||
// (GoToPrevCut / GoToNextCut / GoToStart / GoToEnd) dispatch before
|
||||
// the controls' element key handlers and would shadow the spin
|
||||
// box's arrow keys and the combo's selection keys.
|
||||
cx.update(|cx| cx.clear_key_bindings());
|
||||
|
||||
let mut cx = VisualTestContext::from_window(window.into(), cx).into_mut();
|
||||
// The connection-string row only exists for the PostgreSQL backend.
|
||||
assert!(
|
||||
cx.debug_bounds("preferences-storage-pg-url").is_none(),
|
||||
"the PostgreSQL connection-string row is hidden for SQLite"
|
||||
);
|
||||
|
||||
// Step the cache-ahead spin box once (8 frames): 120 → 128 lands in
|
||||
// the config the playback pre-render window reads.
|
||||
let ahead = cx
|
||||
.debug_bounds("preferences-cache-ahead")
|
||||
.expect("the cache-ahead row is rendered");
|
||||
cx.simulate_click(ahead.center(), gpui::Modifiers::none());
|
||||
cx.run_until_parked();
|
||||
cx.dispatch_keystroke(window.into(), gpui::Keystroke::parse("up").unwrap());
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
crate::oakui::real::config_get_int("PlaybackPreRenderFrames", 120),
|
||||
128,
|
||||
"the cache-ahead spin box writes its stepped value to the config"
|
||||
);
|
||||
|
||||
// Re-select the storage backend: PostgreSQL (the combo starts on
|
||||
// SQLite; with the popup closed the arrow keys drive the selection).
|
||||
let backend = cx
|
||||
.debug_bounds("preferences-storage-backend")
|
||||
.expect("the storage backend row is rendered");
|
||||
cx.simulate_click(backend.center(), gpui::Modifiers::none());
|
||||
cx.run_until_parked();
|
||||
cx.dispatch_keystroke(window.into(), gpui::Keystroke::parse("escape").unwrap());
|
||||
cx.run_until_parked();
|
||||
cx.dispatch_keystroke(window.into(), gpui::Keystroke::parse("down").unwrap());
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
crate::oakui::real::config_get_string("Storage/Backend"),
|
||||
"pg",
|
||||
"selecting PostgreSQL writes Storage/Backend to the config"
|
||||
);
|
||||
// A fresh draw reveals the connection-string row.
|
||||
cx.update(|window, cx| window.draw(cx).clear());
|
||||
cx.run_until_parked();
|
||||
cx.debug_bounds("preferences-storage-pg-url")
|
||||
.expect("the PostgreSQL connection-string row appears after the switch");
|
||||
}
|
||||
|
||||
/// Opening File → Project Properties… must not crash: the content is
|
||||
/// built with the engine and the modal is layered onto the shell (the
|
||||
/// same deferred-build path as Preferences).
|
||||
|
||||
@@ -46,9 +46,11 @@ use crate::oakui::real::{
|
||||
config_get_bool, config_get_int, config_get_string, config_set_bool, config_set_int,
|
||||
config_set_string, encoding_formats, proxy_dividers, renderer_backends, set_audio_input_device,
|
||||
set_audio_output_device, set_theme_dark, theme_is_dark, CONFIG_KEY_DEFAULT_TRANSITION_SEC,
|
||||
CONFIG_KEY_DISK_CACHE_PATH, CONFIG_KEY_FFMPEG_PATH, CONFIG_KEY_PROXY_DIVIDER,
|
||||
CONFIG_KEY_RENDERER_BACKEND, CONFIG_KEY_SNAPSHOT_INTERVAL_SEC, CONFIG_KEY_USE_PROXY,
|
||||
DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC, EXPORT_FORMAT_MP4,
|
||||
CONFIG_KEY_DISK_CACHE_PATH, CONFIG_KEY_FFMPEG_PATH, CONFIG_KEY_PG_URL,
|
||||
CONFIG_KEY_PREVIEW_WINDOW, CONFIG_KEY_PROXY_DIVIDER, CONFIG_KEY_RENDERER_BACKEND,
|
||||
CONFIG_KEY_SNAPSHOT_INTERVAL_SEC, CONFIG_KEY_STORAGE_BACKEND, CONFIG_KEY_USE_PROXY,
|
||||
DEFAULT_PREVIEW_WINDOW_FORWARD, DEFAULT_SNAPSHOT_INTERVAL_SEC, DEFAULT_TRANSITION_SEC,
|
||||
EXPORT_FORMAT_MP4,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -77,7 +79,12 @@ impl gpui::EventEmitter<PreferencesEvent> for PreferencesContent {}
|
||||
///
|
||||
/// * **常规 General** — language, theme.
|
||||
/// * **渲染 Rendering** — the renderer backend.
|
||||
/// * **缓存 Cache** — the disk cache directory (`DiskCachePath`).
|
||||
/// * **缓存 Cache** — the disk cache directory (`DiskCachePath`) and the
|
||||
/// playback pre-render window (`PlaybackPreRenderFrames`, the "cache
|
||||
/// ahead" frames the preview scheduler fills during playback).
|
||||
/// * **存储 Storage** — the write-through library backend
|
||||
/// (`Storage/Backend`, SQLite / PostgreSQL) and the PostgreSQL
|
||||
/// connection string (`Storage/PgUrl`).
|
||||
/// * **代理 Proxy** — use proxy media (`UseProxyMedia`), proxy resolution
|
||||
/// divider (`ProxyDivider`).
|
||||
/// * **项目 Project** — the snapshot interval (`Storage/SnapshotIntervalSec`,
|
||||
@@ -94,6 +101,7 @@ pub struct PreferencesContent {
|
||||
language: Entity<ComboBox>,
|
||||
theme: Entity<ComboBox>,
|
||||
cache_dir: Entity<PathField>,
|
||||
cache_ahead: Entity<SpinBox>,
|
||||
use_proxy: Entity<CheckBox>,
|
||||
hw_decode: Entity<CheckBox>,
|
||||
proxy_divider: Entity<ComboBox>,
|
||||
@@ -103,6 +111,11 @@ pub struct PreferencesContent {
|
||||
transition_length: Entity<SpinBox>,
|
||||
audio_output: Entity<ComboBox>,
|
||||
audio_input: Entity<ComboBox>,
|
||||
storage_backend: Entity<ComboBox>,
|
||||
storage_pg_url: Entity<PathField>,
|
||||
/// Whether the write-through library uses PostgreSQL (drives the
|
||||
/// visibility of the connection-string field).
|
||||
storage_is_pg: bool,
|
||||
/// The backend options, in display order.
|
||||
backends: Vec<&'static str>,
|
||||
/// The proxy divider options, in display order (1 = full resolution).
|
||||
@@ -211,6 +224,33 @@ impl PreferencesContent {
|
||||
let configured_cache = config_get_string(CONFIG_KEY_DISK_CACHE_PATH);
|
||||
cache_dir.update(cx, |field, cx| field.set_path(configured_cache, cx));
|
||||
|
||||
// --- 缓存 Cache: the playback pre-render window (cache ahead) ------
|
||||
// The number of frames the preview scheduler fills ahead of the
|
||||
// playhead during playback (`PlaybackPreRenderFrames`, consumed by
|
||||
// `update_preview_window` every playback tick — no restart needed).
|
||||
let cache_ahead = cx.new(|cx| {
|
||||
let current = config_get_int(
|
||||
CONFIG_KEY_PREVIEW_WINDOW,
|
||||
DEFAULT_PREVIEW_WINDOW_FORWARD,
|
||||
)
|
||||
.clamp(8, 1200);
|
||||
SpinBox::new(
|
||||
10,
|
||||
SliderModel::new(ValueKind::Integer, 8.0, 1200.0, 8.0, current as f64),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&cache_ahead, |_this, _spin, event: &SpinBoxEvent, cx| {
|
||||
let value = match event {
|
||||
SpinBoxEvent::ValueChanged { value, .. }
|
||||
| SpinBoxEvent::EditCommitted { value, .. } => value.to_f64() as i64,
|
||||
};
|
||||
config_set_int(CONFIG_KEY_PREVIEW_WINDOW, value);
|
||||
let _ = cx;
|
||||
})
|
||||
.detach();
|
||||
|
||||
// --- 代理 Proxy -----------------------------------------------------
|
||||
let use_proxy = cx.new(|cx| {
|
||||
CheckBox::new(
|
||||
@@ -412,11 +452,50 @@ impl PreferencesContent {
|
||||
})
|
||||
.detach();
|
||||
|
||||
// --- 存储 Storage: the write-through library backend ----------------
|
||||
// `Storage/Backend` + `Storage/PgUrl` are read by oakstorage's
|
||||
// write-through when a project binds (per-project library session),
|
||||
// so a change applies to projects opened afterwards.
|
||||
let storage_is_pg = config_get_string(CONFIG_KEY_STORAGE_BACKEND) == "pg";
|
||||
let storage_backend = cx.new(|cx| {
|
||||
let options = vec![
|
||||
ComboBoxOption::new(0, "SQLite"),
|
||||
ComboBoxOption::new(1, "PostgreSQL"),
|
||||
];
|
||||
ComboBox::new(11, options, window, cx)
|
||||
});
|
||||
cx.subscribe(&storage_backend, |this, _combo, event: &ComboBoxEvent, cx| {
|
||||
match *event {
|
||||
ComboBoxEvent::Selected { value } => {
|
||||
let backend = if value == 1 { "pg" } else { "sqlite" };
|
||||
this.storage_is_pg = backend == "pg";
|
||||
config_set_string(CONFIG_KEY_STORAGE_BACKEND, backend);
|
||||
// Re-render so the connection-string row shows/hides with
|
||||
// the selection.
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
storage_backend.update(cx, |combo, cx| {
|
||||
combo.set_selected(Some(if storage_is_pg { 1 } else { 0 }), cx)
|
||||
});
|
||||
let storage_pg_url = cx.new(|cx| {
|
||||
let editor = cx.new(|cx| EditableTextState::new(StringStorage::default(), cx));
|
||||
PathField {
|
||||
editor,
|
||||
enabled: true,
|
||||
}
|
||||
});
|
||||
let configured_pg_url = config_get_string(CONFIG_KEY_PG_URL);
|
||||
storage_pg_url.update(cx, |field, cx| field.set_path(configured_pg_url, cx));
|
||||
|
||||
Self {
|
||||
backend,
|
||||
language,
|
||||
theme,
|
||||
cache_dir,
|
||||
cache_ahead,
|
||||
use_proxy,
|
||||
hw_decode,
|
||||
proxy_divider,
|
||||
@@ -426,6 +505,9 @@ impl PreferencesContent {
|
||||
transition_length,
|
||||
audio_output,
|
||||
audio_input,
|
||||
storage_backend,
|
||||
storage_pg_url,
|
||||
storage_is_pg,
|
||||
backends,
|
||||
dividers,
|
||||
output_devices,
|
||||
@@ -440,6 +522,13 @@ impl PreferencesContent {
|
||||
config_set_string(crate::oakui::displaycolor::CONFIG_KEY_CUSTOM_ICC, &path);
|
||||
}
|
||||
|
||||
/// Commits the PostgreSQL connection-string field to the config (called
|
||||
/// by the host when the dialog closes, like the cache directory).
|
||||
pub fn commit_storage_pg_url(&self, cx: &App) {
|
||||
let url = self.storage_pg_url.read(cx).path(cx).trim().to_string();
|
||||
config_set_string(CONFIG_KEY_PG_URL, &url);
|
||||
}
|
||||
|
||||
/// Opens the platform file picker for a custom ICC profile.
|
||||
fn browse_display_icc(&mut self, cx: &mut Context<Self>) {
|
||||
let receiver = cx.prompt_for_paths(gpui::PathPromptOptions {
|
||||
@@ -650,6 +739,43 @@ impl Render for PreferencesContent {
|
||||
})),
|
||||
),
|
||||
))
|
||||
// 缓存 Cache: cache ahead (playback pre-render window)
|
||||
.child(form_row(
|
||||
&colors,
|
||||
i18n::tr("preferences.cache.ahead").into(),
|
||||
div()
|
||||
.debug_selector(|| "preferences-cache-ahead".into())
|
||||
.child(self.cache_ahead.clone()),
|
||||
))
|
||||
// 存储 Storage
|
||||
.child(section_header(
|
||||
&colors,
|
||||
i18n::tr("preferences.section.storage").into(),
|
||||
))
|
||||
.child(form_row(
|
||||
&colors,
|
||||
i18n::tr("preferences.storage.backend").into(),
|
||||
div()
|
||||
.debug_selector(|| "preferences-storage-backend".into())
|
||||
.child(self.storage_backend.clone()),
|
||||
))
|
||||
.child(if self.storage_is_pg {
|
||||
form_row(
|
||||
&colors,
|
||||
i18n::tr("preferences.storage.pg_url").into(),
|
||||
div()
|
||||
.debug_selector(|| "preferences-storage-pg-url".into())
|
||||
.child(self.storage_pg_url.clone()),
|
||||
)
|
||||
} else {
|
||||
div()
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.text_color(colors.disabled)
|
||||
.text_xs()
|
||||
.child(i18n::tr("preferences.storage.restart_hint")),
|
||||
)
|
||||
// 代理 Proxy
|
||||
.child(section_header(
|
||||
&colors,
|
||||
@@ -1637,11 +1763,13 @@ impl PreferencesDialogContent {
|
||||
}
|
||||
|
||||
/// Commits the general tab's free-text fields (the cache directory, the
|
||||
/// custom ICC path), for the host when the dialog closes.
|
||||
/// custom ICC path, the PostgreSQL connection string), for the host when
|
||||
/// the dialog closes.
|
||||
pub fn commit_cache_dir(&self, cx: &App) {
|
||||
let general = self.general.read(cx);
|
||||
general.commit_cache_dir(cx);
|
||||
general.commit_display_icc_path(cx);
|
||||
general.commit_storage_pg_url(cx);
|
||||
}
|
||||
|
||||
/// The keyboard tab's action-row count (tests).
|
||||
@@ -2558,6 +2686,54 @@ fn selection_step(visible: &[usize], current: Option<usize>, delta: i32) -> Opti
|
||||
Some(visible[next])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// About (帮助 → 关于 Oak)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The About dialog's content: the app name + version, the GPL-3.0 license
|
||||
/// line and the Olive fork notice (the C++ AboutDialog's text block; the
|
||||
/// patrons scroller is not ported).
|
||||
pub struct AboutContent;
|
||||
|
||||
impl AboutContent {
|
||||
/// Builds the static about text.
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AboutContent {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = cx.default_colors().clone();
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_color(colors.text)
|
||||
.font_weight(gpui::FontWeight::BOLD)
|
||||
.child(format!("Oak Video Editor {}", env!("CARGO_PKG_VERSION"))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(colors.text)
|
||||
.child(i18n::tr("about.description")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(colors.text)
|
||||
.child(i18n::tr("about.thanks")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(colors.disabled)
|
||||
.text_xs()
|
||||
.child(i18n::tr("about.fork_notice")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -253,9 +253,9 @@ enum FullResTarget {
|
||||
// their slots back to the workers.
|
||||
|
||||
/// The default forward pre-render window (frames).
|
||||
const DEFAULT_PREVIEW_WINDOW_FORWARD: i64 = 120;
|
||||
pub const DEFAULT_PREVIEW_WINDOW_FORWARD: i64 = 120;
|
||||
/// Config key for the forward pre-render window size (frames).
|
||||
const CONFIG_KEY_PREVIEW_WINDOW: &str = "PlaybackPreRenderFrames";
|
||||
pub const CONFIG_KEY_PREVIEW_WINDOW: &str = "PlaybackPreRenderFrames";
|
||||
|
||||
/// One monitor's playback pre-render state (UI-thread-owned; the completions
|
||||
/// delivered by the dispatcher's poll run on the UI thread too).
|
||||
@@ -5403,6 +5403,10 @@ pub fn audio_init_from_config() {
|
||||
/// The config key selecting the storage backend (see
|
||||
/// `crates/oakstorage/src/writethrough.rs`).
|
||||
pub const CONFIG_KEY_STORAGE_BACKEND: &str = "Storage/Backend";
|
||||
/// The config key holding the PostgreSQL connection string (`Storage/PgUrl`,
|
||||
/// `user:pass@host:5432/dbname`; used when the backend is `"pg"` — see
|
||||
/// `crates/oakstorage/src/writethrough.rs`).
|
||||
pub const CONFIG_KEY_PG_URL: &str = "Storage/PgUrl";
|
||||
|
||||
/// Enables the SQLite write-through library unless the user configured the
|
||||
/// backend explicitly (any existing value — including "off" — wins over the
|
||||
|
||||
+1
-1
Submodule gpui updated: 79b3a8fbd4...7497035c12
Reference in New Issue
Block a user