project file: new-project dialog (name + sequence format), open prompt lists formats
- 文件 → 新建项目 opens an in-app dialog: project name + the first sequence's format (preset / custom / interlaced, reusing SequenceFormatFields); OK creates the library project then seeds the first sequence from the chosen format - 打开项目 prompt advertises the supported project formats (.ove / .otio / .fcpxml) - manager.new.project_name across the 8 i18n packs - action-search first-item test follows the new flow (New Project now opens its dialog)
This commit is contained in:
+2
-1
@@ -183,6 +183,7 @@
|
||||
"manager.title": "Projektmanager"
|
||||
"manager.new": "Neues Projekt"
|
||||
"manager.new.default_name": "Unbenanntes Projekt"
|
||||
"manager.new.project_name": "Projektname"
|
||||
"manager.open": "Öffnen"
|
||||
"manager.rename": "Umbenennen…"
|
||||
"manager.rename.title": "Projekt umbenennen"
|
||||
@@ -303,7 +304,7 @@
|
||||
"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.open.title": "Projekt öffnen (.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "Material importieren"
|
||||
"preferences.title": "Einstellungen"
|
||||
"preferences.section.general": "Allgemein"
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"manager.title": "Project Manager"
|
||||
"manager.new": "New Project"
|
||||
"manager.new.default_name": "Untitled Project"
|
||||
"manager.new.project_name": "Project Name"
|
||||
"manager.open": "Open"
|
||||
"manager.rename": "Rename…"
|
||||
"manager.rename.title": "Rename Project"
|
||||
@@ -301,7 +302,7 @@
|
||||
"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.open.title": "Open Project (.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "Import Footage"
|
||||
"preferences.title": "Preferences"
|
||||
"preferences.section.general": "General"
|
||||
|
||||
+2
-1
@@ -183,6 +183,7 @@
|
||||
"manager.title": "Administrador de proyectos"
|
||||
"manager.new": "Nuevo proyecto"
|
||||
"manager.new.default_name": "Proyecto sin título"
|
||||
"manager.new.project_name": "Nombre del proyecto"
|
||||
"manager.open": "Abrir"
|
||||
"manager.rename": "Cambiar nombre…"
|
||||
"manager.rename.title": "Cambiar nombre del proyecto"
|
||||
@@ -303,7 +304,7 @@
|
||||
"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.open.title": "Abrir proyecto (.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "Importar material"
|
||||
"preferences.title": "Preferencias"
|
||||
"preferences.section.general": "General"
|
||||
|
||||
+2
-1
@@ -183,6 +183,7 @@
|
||||
"manager.title": "Gestionnaire de projets"
|
||||
"manager.new": "Nouveau projet"
|
||||
"manager.new.default_name": "Projet sans titre"
|
||||
"manager.new.project_name": "Nom du projet"
|
||||
"manager.open": "Ouvrir"
|
||||
"manager.rename": "Renommer…"
|
||||
"manager.rename.title": "Renommer le projet"
|
||||
@@ -303,7 +304,7 @@
|
||||
"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.open.title": "Ouvrir le projet (.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "Importer des médias"
|
||||
"preferences.title": "Préférences"
|
||||
"preferences.section.general": "Général"
|
||||
|
||||
+2
-1
@@ -183,6 +183,7 @@
|
||||
"manager.title": "プロジェクトマネージャー"
|
||||
"manager.new": "新規プロジェクト"
|
||||
"manager.new.default_name": "無題のプロジェクト"
|
||||
"manager.new.project_name": "プロジェクト名"
|
||||
"manager.open": "開く"
|
||||
"manager.rename": "名前を変更…"
|
||||
"manager.rename.title": "プロジェクトの名前を変更"
|
||||
@@ -303,7 +304,7 @@
|
||||
"about.description": "Oak Video Editor はフリーでオープンソースのノンリニア動画エディタです。本ソフトウェアは GNU GPL バージョン 3 でライセンスされています。"
|
||||
"about.thanks": "プロジェクト初期に広く紹介してくれたコミュニティプロモーター Enzo GD に心より感謝します。"
|
||||
"about.fork_notice": "本プロジェクトは Olive Video Editor のフォークです。"
|
||||
"file.open.title": "プロジェクトを開く"
|
||||
"file.open.title": "プロジェクトを開く(.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "素材を読み込み"
|
||||
"preferences.title": "環境設定"
|
||||
"preferences.section.general": "一般"
|
||||
|
||||
+2
-1
@@ -183,6 +183,7 @@
|
||||
"manager.title": "Gerenciador de projetos"
|
||||
"manager.new": "Novo projeto"
|
||||
"manager.new.default_name": "Projeto sem título"
|
||||
"manager.new.project_name": "Nome do projeto"
|
||||
"manager.open": "Abrir"
|
||||
"manager.rename": "Renomear…"
|
||||
"manager.rename.title": "Renomear projeto"
|
||||
@@ -303,7 +304,7 @@
|
||||
"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.open.title": "Abrir projeto (.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "Importar mídia"
|
||||
"preferences.title": "Preferências"
|
||||
"preferences.section.general": "Geral"
|
||||
|
||||
+2
-1
@@ -183,6 +183,7 @@
|
||||
"manager.title": "Диспетчер проектов"
|
||||
"manager.new": "Новый проект"
|
||||
"manager.new.default_name": "Проект без названия"
|
||||
"manager.new.project_name": "Название проекта"
|
||||
"manager.open": "Открыть"
|
||||
"manager.rename": "Переименовать…"
|
||||
"manager.rename.title": "Переименовать проект"
|
||||
@@ -303,7 +304,7 @@
|
||||
"about.description": "Oak Video Editor — свободный нелинейный видеоредактор с открытым исходным кодом. Программа распространяется по лицензии GNU GPL версии 3."
|
||||
"about.thanks": "Особая благодарность Enzo GD, нашему Community Promoter, за щедрую поддержку в распространении этого проекта в первые дни."
|
||||
"about.fork_notice": "Этот проект является форком Olive Video Editor."
|
||||
"file.open.title": "Открыть проект"
|
||||
"file.open.title": "Открыть проект (.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "Импорт медиафайлов"
|
||||
"preferences.title": "Настройки"
|
||||
"preferences.section.general": "Общие"
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"manager.title": "项目管理器"
|
||||
"manager.new": "新建项目"
|
||||
"manager.new.default_name": "未命名项目"
|
||||
"manager.new.project_name": "项目名称"
|
||||
"manager.open": "打开"
|
||||
"manager.rename": "重命名…"
|
||||
"manager.rename.title": "重命名工程"
|
||||
@@ -301,7 +302,7 @@
|
||||
"about.description": "Oak 视频编辑器是一款自由开源的非线性视频编辑器。本软件以 GNU GPL 第三版许可证发布。"
|
||||
"about.thanks": "特别感谢我们的社区推广者 Enzo GD,感谢他在项目早期为传播本项目所给予的慷慨支持。"
|
||||
"about.fork_notice": "本项目是 Olive 视频编辑器的 fork。"
|
||||
"file.open.title": "打开项目"
|
||||
"file.open.title": "打开项目(.ove / .otio / .fcpxml)"
|
||||
"file.import_footage.title": "导入素材"
|
||||
"preferences.title": "偏好设置"
|
||||
"preferences.section.general": "常规"
|
||||
|
||||
+79
-12
@@ -142,6 +142,8 @@ mod modal_ids {
|
||||
pub const RENAME: usize = 18;
|
||||
/// The project export dialog (文件 → 导出工程文件…).
|
||||
pub const EXPORT_PROJECT_DFD: usize = 19;
|
||||
/// The new-project dialog (文件 → 新建项目).
|
||||
pub const NEW_PROJECT: usize = 20;
|
||||
}
|
||||
|
||||
/// What a picked platform-dialog path should do.
|
||||
@@ -247,6 +249,11 @@ enum ModalState<E: AppEngine> {
|
||||
modal: Entity<Modal>,
|
||||
content: Entity<crate::dialogs::ExportProjectDialogContent>,
|
||||
},
|
||||
/// The new-project dialog (文件 → 新建项目): name + sequence format.
|
||||
NewProject {
|
||||
modal: Entity<Modal>,
|
||||
content: Entity<crate::dialogs::NewProjectContent>,
|
||||
},
|
||||
}
|
||||
|
||||
/// A running export: the session the tick loop drains for progress.
|
||||
@@ -275,6 +282,7 @@ impl<E: AppEngine> ModalState<E> {
|
||||
| ModalState::MulticamWizard { modal, .. }
|
||||
| ModalState::EntryRename { modal, .. } => Some(modal.clone()),
|
||||
| ModalState::ExportProjectDfd { modal, .. } => Some(modal.clone()),
|
||||
| ModalState::NewProject { modal, .. } => Some(modal.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1019,7 +1027,7 @@ impl<E: AppEngine> OakApp<E> {
|
||||
use crate::actions::ActionId as A;
|
||||
match action {
|
||||
// --- File ------------------------------------------------------
|
||||
A::NewProject => self.new_project(cx),
|
||||
A::NewProject => self.open_new_project(cx),
|
||||
A::OpenProject => self.open_file_dialog(FileAction::Open, cx),
|
||||
A::OpenFromLibrary | A::ProjectManager => self.show_project_manager(cx),
|
||||
A::Import => self.open_file_dialog(FileAction::ImportFootage, cx),
|
||||
@@ -1257,14 +1265,61 @@ impl<E: AppEngine> OakApp<E> {
|
||||
/// 新建项目: creates a blank project in the library and opens it (the
|
||||
/// write-through persists it from the first edit). Falls back to the
|
||||
/// engine's plain new-project path when the library is unavailable.
|
||||
fn new_project(&mut self, cx: &mut Context<Self>) {
|
||||
let name = crate::i18n::tr("manager.new.default_name").to_string();
|
||||
let result = self
|
||||
.engine
|
||||
.update(cx, |engine, cx| engine.library_create_project(&name, cx));
|
||||
if let Err(err) = result {
|
||||
println!("[file] library create failed ({err}); plain new project");
|
||||
self.engine.update(cx, |engine, cx| engine.new_project(cx));
|
||||
fn open_new_project(&mut self, cx: &mut Context<Self>) {
|
||||
if !matches!(self.modal, ModalState::None) {
|
||||
return;
|
||||
}
|
||||
self.spawn_modal(cx, move |window, app| {
|
||||
let content = app.new(|cx| crate::dialogs::NewProjectContent::new(window, cx));
|
||||
let modal = app.new(|cx| {
|
||||
Modal::new(
|
||||
modal_ids::NEW_PROJECT,
|
||||
ModalOptions::new(
|
||||
crate::i18n::tr("menu.file.new_project"),
|
||||
px(480.0),
|
||||
)
|
||||
.with_button(DialogButton::primary(crate::i18n::tr("dialog.ok")))
|
||||
.with_button(DialogButton::new(
|
||||
crate::i18n::tr("dialog.cancel"),
|
||||
gpui_widgets::dialog::DialogButtonRole::Secondary,
|
||||
)),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.with_content(content.clone())
|
||||
});
|
||||
ModalState::NewProject { modal, content }
|
||||
});
|
||||
}
|
||||
|
||||
/// Creates the project from the dialog's name + format, then seeds its
|
||||
/// first sequence (the old `new_project` path kept for manager fallback).
|
||||
fn commit_new_project(&mut self, content: &Entity<crate::dialogs::NewProjectContent>, cx: &mut Context<Self>) {
|
||||
let name = content.read(cx).name(cx).to_string();
|
||||
let format = content.read(cx).format(cx);
|
||||
let interlaced = content.read(cx).interlaced(cx);
|
||||
let engine = self.engine.clone();
|
||||
let result = engine.update(cx, |engine, cx| {
|
||||
engine.library_create_project(&name, cx)
|
||||
});
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.close_modal(cx);
|
||||
let engine = self.engine.clone();
|
||||
engine.update(cx, |engine, cx| {
|
||||
let _ = engine.create_sequence_with_params(
|
||||
crate::i18n::tr("manager.new.default_name").to_string(),
|
||||
format,
|
||||
interlaced,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
println!("[file] library create failed ({err}); plain new project");
|
||||
self.close_modal(cx);
|
||||
self.engine.update(cx, |engine, cx| engine.new_project(cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3040,6 +3095,16 @@ impl<E: AppEngine> OakApp<E> {
|
||||
}
|
||||
}
|
||||
}
|
||||
modal_ids::NEW_PROJECT => {
|
||||
if let ModalState::NewProject { content, .. } = &self.modal {
|
||||
if *button == 0 {
|
||||
let content = content.clone();
|
||||
self.commit_new_project(&content, cx);
|
||||
} else {
|
||||
self.close_modal(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
modal_ids::SEQUENCE_PROPERTIES => {
|
||||
if let ModalState::SequenceProperties { content, .. } = &self.modal {
|
||||
let content = content.clone();
|
||||
@@ -4845,15 +4910,17 @@ mod tests {
|
||||
);
|
||||
|
||||
// Down selects the first listed action, Enter runs it. The empty query
|
||||
// lists every menu-bar action, so the first one is New Project.
|
||||
// lists every menu-bar action, so the first one is New Project — the
|
||||
// v0.5 flow opens the new-project dialog (name + format), so the
|
||||
// search dialog hands off to it.
|
||||
cx.dispatch_keystroke(window.into(), gpui::Keystroke::parse("down").unwrap());
|
||||
cx.run_until_parked();
|
||||
cx.dispatch_keystroke(window.into(), gpui::Keystroke::parse("enter").unwrap());
|
||||
cx.run_until_parked();
|
||||
|
||||
assert!(
|
||||
cx.read(|app| matches!(root.read(app).modal, ModalState::None)),
|
||||
"executing the selected action closes the dialog"
|
||||
cx.read(|app| matches!(root.read(app).modal, ModalState::NewProject { .. })),
|
||||
"the search dialog hands off to the new-project dialog"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3952,6 +3952,64 @@ impl Render for ExportProjectDialogContent {
|
||||
}
|
||||
}
|
||||
|
||||
/// The new-project dialog (文件 → 新建项目): project name + the first
|
||||
/// sequence's format (preset / custom width-height-rate / interlaced).
|
||||
/// The host reads [`Self::name`] + [`Self::format`]; the engine's
|
||||
/// library_create_project then seeds the first sequence.
|
||||
pub struct NewProjectContent {
|
||||
name: Entity<TextValue>,
|
||||
format: Entity<SequenceFormatFields>,
|
||||
}
|
||||
|
||||
impl NewProjectContent {
|
||||
/// Builds the dialog (default name, HD 1080p25).
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let name = cx.new(|cx| {
|
||||
let editor = cx.new(|cx| EditableTextState::new(StringStorage::default(), cx));
|
||||
TextValue { editor }
|
||||
});
|
||||
name.update(cx, |field, cx| {
|
||||
field.set_value(i18n::tr("manager.new.default_name"), cx)
|
||||
});
|
||||
let format = cx.new(|cx| {
|
||||
SequenceFormatFields::build(SequenceFormatSeed::hd_1080p25(), window, cx)
|
||||
});
|
||||
Self { name, format }
|
||||
}
|
||||
|
||||
/// The project name entered.
|
||||
pub fn name(&self, cx: &App) -> SharedString {
|
||||
self.name.read(cx).value(cx)
|
||||
}
|
||||
|
||||
/// The format selected.
|
||||
pub fn format(&self, cx: &App) -> crate::oakui::engine::VideoFormat {
|
||||
self.format.read(cx).format(cx)
|
||||
}
|
||||
|
||||
/// The interlaced flag.
|
||||
pub fn interlaced(&self, cx: &App) -> bool {
|
||||
self.format.read(cx).interlaced(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for NewProjectContent {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = cx.default_colors().clone();
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.w_full()
|
||||
.child(form_row(
|
||||
&colors,
|
||||
i18n::tr("manager.new.project_name").into(),
|
||||
self.name.clone(),
|
||||
))
|
||||
.child(self.format.read(cx).rows(&colors))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user