Skip to content

Commit 312d8f5

Browse files
Merge pull request #2215 from CapSoftware/desktop-optimisations-etc
improve: speed up desktop editing and export with window and menu fixes
2 parents ca34c3c + 0886c8c commit 312d8f5

53 files changed

Lines changed: 6418 additions & 595 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/sync-tests.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ on:
2424
- "crates/timestamp/**"
2525
- "crates/rendering/**"
2626
- "crates/editor/**"
27+
- "crates/export/**"
2728
- "crates/audio/**"
2829
- "crates/media-info/**"
2930
- "crates/project/**"
@@ -139,11 +140,14 @@ jobs:
139140
cargo test --locked -p cap-recording --lib -- --test-threads=1
140141
# --nocapture so a WARP-adapter notch skip prints instead of
141142
# looking identical to a pass in the CI log.
142-
cargo test --locked -p cap-rendering -- --nocapture
143+
cargo test --locked -p cap-rendering -- --nocapture --skip zoom_spring::tests::precompute_cost_is_bounded_for_long_projects
144+
cargo test --locked -p cap-rendering --lib zoom_spring::tests::precompute_cost_is_bounded_for_long_projects -- --exact --nocapture --test-threads=1
143145
144146
- name: Editor audio playback and export regressions
145147
shell: bash
146148
run: |
149+
cargo test --locked -p cap-audio --lib
150+
cargo test --locked -p cap-export --lib
147151
cargo test --locked -p cap-editor --lib audio::tests::
148152
cargo test --locked -p cap-editor --lib audio_output::tests::
149153
cargo test --locked -p cap-editor --lib playback::tests::

apps/desktop-gpui/src/app_windows.rs

Lines changed: 41 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4463,10 +4463,7 @@ fn load_editor_project(path: PathBuf, handle: WindowHandle<EditorWindow>, cx: &m
44634463
);
44644464
log_timeline_model(&summary.timeline);
44654465
let recordings = summary.recordings.clone();
4466-
if handle
4467-
.update(cx, |view, window, cx| view.set_summary(summary, window, cx))
4468-
.is_err()
4469-
{
4466+
if handle.update(cx, |_, _, _| ()).is_err() {
44704467
return;
44714468
}
44724469

@@ -4548,8 +4545,35 @@ fn load_editor_project(path: PathBuf, handle: WindowHandle<EditorWindow>, cx: &m
45484545
};
45494546

45504547
tracing::info!(path = %path.display(), "editor instance ready");
4548+
let (total, config) = {
4549+
let config = instance.project_config.1.borrow().clone();
4550+
let total = config
4551+
.timeline
4552+
.as_ref()
4553+
.map_or(0.0, |timeline| timeline.duration());
4554+
(total, config)
4555+
};
4556+
let has_camera = instance
4557+
.recordings
4558+
.segments
4559+
.iter()
4560+
.any(|segment| segment.camera.is_some());
4561+
let multiple_clips = instance.recordings.segments.len() > 1;
4562+
log_timeline_model(&editor_timeline::TimelineModel::build(
4563+
&config,
4564+
has_camera,
4565+
multiple_clips,
4566+
));
45514567
if handle
4552-
.update(cx, |view, _window, _cx| view.set_instance(instance.clone()))
4568+
.update(cx, |view, window, cx| {
4569+
// Loading controls can queue a save before the engine is ready.
4570+
// Publish the loaded config and instance together so those edits
4571+
// cannot replace the saved project with the initial defaults.
4572+
view.pending_save().borrow_mut().discard();
4573+
view.set_summary(summary, window, cx);
4574+
view.set_project(config, window, cx);
4575+
view.set_instance(instance.clone());
4576+
})
45534577
.is_err()
45544578
{
45554579
instance.dispose().await;
@@ -4659,44 +4683,6 @@ fn load_editor_project(path: PathBuf, handle: WindowHandle<EditorWindow>, cx: &m
46594683
})
46604684
.detach();
46614685

4662-
// `totalDuration()` (`context.ts:1374-1380`). Read off the instance
4663-
// rather than the pre-flight, because `EditorInstance::new`
4664-
// synthesises a timeline for a raw bundle -- and `timeline.duration()`
4665-
// is exactly what the playback engine stops at
4666-
// (`playback.rs:560-570`).
4667-
//
4668-
// The whole track model comes from the same read: the config the
4669-
// instance actually loaded is the one being rendered, holds, clip
4670-
// offsets and all. E4 hands the window the config itself rather than
4671-
// the derived model, because it is what every edit mutates and what
4672-
// the debounced save writes back.
4673-
let (total, config) = {
4674-
let config = instance.project_config.1.borrow().clone();
4675-
let total = config
4676-
.timeline
4677-
.as_ref()
4678-
.map_or(0.0, |timeline| timeline.duration());
4679-
(total, config)
4680-
};
4681-
{
4682-
let has_camera = instance
4683-
.recordings
4684-
.segments
4685-
.iter()
4686-
.any(|segment| segment.camera.is_some());
4687-
let multiple_clips = instance.recordings.segments.len() > 1;
4688-
log_timeline_model(&editor_timeline::TimelineModel::build(
4689-
&config,
4690-
has_camera,
4691-
multiple_clips,
4692-
));
4693-
}
4694-
if handle
4695-
.update(cx, |view, window, cx| view.set_project(config, window, cx))
4696-
.is_err()
4697-
{
4698-
return;
4699-
}
47004686
load_editor_waveforms(instance.clone(), handle, cx);
47014687

47024688
if handle
@@ -4805,14 +4791,7 @@ fn load_editor_waveforms(
48054791
(&segment.audio, &mut mic),
48064792
(&segment.system_audio, &mut system),
48074793
] {
4808-
match loader.get().await {
4809-
Ok(Some(audio)) => {
4810-
out.push((audio.samples().to_vec(), audio.channels()))
4811-
}
4812-
// A failed track is an empty waveform; playback and
4813-
// export surface the actual error.
4814-
_ => out.push((Vec::new(), 1)),
4815-
}
4794+
out.push(loader.get().await.ok().flatten());
48164795
}
48174796
}
48184797
(mic, system)
@@ -4824,15 +4803,21 @@ fn load_editor_waveforms(
48244803
let peaks = cx
48254804
.background_executor()
48264805
.spawn(async move {
4827-
let extract = |tracks: Vec<(Vec<f32>, u16)>| {
4806+
let [mic, system] = [mic, system].map(|tracks| {
48284807
tracks
48294808
.into_iter()
4830-
.map(|(samples, channels)| {
4831-
Arc::new(editor_timeline::waveform_peaks(&samples, channels))
4809+
.map(|audio| {
4810+
Arc::new(match audio {
4811+
Some(audio) => editor_timeline::waveform_peaks(
4812+
audio.samples(),
4813+
audio.channels(),
4814+
),
4815+
None => Vec::new(),
4816+
})
48324817
})
48334818
.collect::<Vec<_>>()
4834-
};
4835-
(extract(mic), extract(system))
4819+
});
4820+
(mic, system)
48364821
})
48374822
.await;
48384823
let _ = handle.update(cx, |view, window, cx| {

apps/desktop-gpui/src/assets.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ mod tests {
360360
include_str!("screenshot_annotations.rs"),
361361
// `ui::SelectionHeader` names the check and the trash itself.
362362
include_str!("ui/selection_header.rs"),
363+
include_str!("ui/radio_cards.rs"),
363364
// The onboarding window's welcome cards and permissions surface; the
364365
// per-permission row glyphs are named on `OSPermission::icon`.
365366
include_str!("onboarding_window.rs"),

apps/desktop-gpui/src/devices.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,11 @@ pub fn list_window_targets() -> Vec<(WindowOption, Window)> {
380380
Window::list()
381381
.into_iter()
382382
.filter_map(|window| {
383+
#[cfg(target_os = "windows")]
384+
if !window.raw_handle().is_valid() || !window.raw_handle().is_on_screen() {
385+
return None;
386+
}
387+
383388
let label = window.name().filter(|name| !name.trim().is_empty())?;
384389
let app = window.owner_name()?;
385390

apps/desktop-gpui/src/editor_clips.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,17 @@ impl EditorWindow {
408408
ui::Button::plain(&self.theme, "clips-pill", variant, ui::ButtonSize::Md)
409409
.icon("icons/clapperboard.svg")
410410
.label("Clips")
411+
.disabled(!self.project_ready())
411412
.height(px(40.))
413+
.radius(px(12.))
412414
.font_weight(FontWeight::MEDIUM)
413415
.on_click(cx.listener(|this, _, window, cx| this.toggle_clips(window, cx)))
414416
}
415417

416418
pub(crate) fn toggle_clips(&mut self, window: &mut Window, cx: &mut Context<Self>) {
419+
if !self.project_ready() {
420+
return;
421+
}
417422
self.set_selection(None, cx);
418423
if self.clips.open {
419424
self.close_clips(window, cx);
@@ -754,6 +759,7 @@ impl EditorWindow {
754759
.px(px(16.))
755760
.w_full()
756761
.h(px(64.))
762+
.rounded_t(px(11.))
757763
.border_b_1()
758764
.border_color(Hsla::from(theme.gray_3))
759765
.text_size(px(14.))
@@ -838,12 +844,13 @@ impl EditorWindow {
838844
.gap(px(8.))
839845
.font_weight(FontWeight::MEDIUM)
840846
.disabled(self.clips.importing)
841-
.on_click(cx.listener(
842-
|this, event: &gpui::ClickEvent, _window, cx| {
847+
.on_open(cx.listener(
848+
|this, bounds: &Bounds<Pixels>, _window, cx| {
843849
if this.clips.importing {
844850
return;
845851
}
846-
this.clips.import_menu = Some(event.position());
852+
this.clips.import_menu =
853+
Some(bounds.bottom_left() + gpui::point(px(0.), px(4.)));
847854
cx.notify();
848855
},
849856
)),
@@ -1200,7 +1207,7 @@ impl EditorWindow {
12001207
}
12011208

12021209
fn begin_editor_recording(&mut self, cx: &mut Context<Self>) -> bool {
1203-
if self.clips.importing {
1210+
if !self.project_ready() || self.clips.importing {
12041211
return false;
12051212
}
12061213
let session = RecordingSession::global(cx);
@@ -1252,6 +1259,13 @@ impl EditorWindow {
12521259
_window: &mut Window,
12531260
cx: &mut Context<Self>,
12541261
) {
1262+
if !self.project_ready() {
1263+
tracing::warn!(
1264+
recording = %recording_dir.display(),
1265+
"the editor is not ready; leaving the recording in the library"
1266+
);
1267+
return;
1268+
}
12551269
if self.clips.importing {
12561270
// A concurrent import owns the bundle merge; the capture stays in
12571271
// the library and can be pulled in through "Existing recording".
@@ -1353,6 +1367,7 @@ impl EditorWindow {
13531367
.child(
13541368
div()
13551369
.id("clips-import-backdrop")
1370+
.occlude()
13561371
.absolute()
13571372
.top_0()
13581373
.left_0()
@@ -1506,7 +1521,7 @@ impl EditorWindow {
15061521
window: &mut Window,
15071522
cx: &mut Context<Self>,
15081523
) {
1509-
if self.clips.importing {
1524+
if !self.project_ready() || self.clips.importing {
15101525
return;
15111526
}
15121527
if self.playing {

apps/desktop-gpui/src/editor_export.rs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,8 +2171,22 @@ async fn run_export(
21712171
builder = builder.with_output_path(path);
21722172
}
21732173

2174-
let base = builder.build().await.map_err(|error| error.to_string())?;
2175-
let total = base.total_frames(fps);
2174+
enum PreparedBase {
2175+
Mp4(cap_export::Mp4ExporterBase),
2176+
Other(ExporterBase),
2177+
}
2178+
let (base, total) = if !cursor_only && format != ExportFormatKind::Gif {
2179+
let base = builder
2180+
.build_for_mp4(cancel.clone())
2181+
.await
2182+
.map_err(|error| error.to_string())?;
2183+
let total = base.total_frames(fps);
2184+
(PreparedBase::Mp4(base), total)
2185+
} else {
2186+
let base = builder.build().await.map_err(|error| error.to_string())?;
2187+
let total = base.total_frames(fps);
2188+
(PreparedBase::Other(base), total)
2189+
};
21762190
let _ = progress_tx.send((0, total));
21772191

21782192
let progress = {
@@ -2189,33 +2203,37 @@ async fn run_export(
21892203
};
21902204

21912205
let resolution = XY::new(width, height);
2192-
if cursor_only {
2193-
MovExportSettings {
2194-
fps,
2195-
resolution_base: resolution,
2196-
cursor_only: true,
2206+
match base {
2207+
PreparedBase::Other(base) if cursor_only => {
2208+
MovExportSettings {
2209+
fps,
2210+
resolution_base: resolution,
2211+
cursor_only: true,
2212+
}
2213+
.export(base, progress)
2214+
.await
21972215
}
2198-
.export(base, progress)
2199-
.await
2200-
} else if format == ExportFormatKind::Gif {
2201-
GifExportSettings {
2202-
fps,
2203-
resolution_base: resolution,
2204-
quality: None,
2216+
PreparedBase::Other(base) => {
2217+
GifExportSettings {
2218+
fps,
2219+
resolution_base: resolution,
2220+
quality: None,
2221+
}
2222+
.export(base, progress)
2223+
.await
22052224
}
2206-
.export(base, progress)
2207-
.await
2208-
} else {
2209-
Mp4ExportSettings {
2210-
fps,
2211-
resolution_base: resolution,
2212-
compression,
2213-
custom_bpp,
2214-
force_ffmpeg_decoder: force,
2215-
optimize_filesize: optimize,
2225+
PreparedBase::Mp4(base) => {
2226+
Mp4ExportSettings {
2227+
fps,
2228+
resolution_base: resolution,
2229+
compression,
2230+
custom_bpp,
2231+
force_ffmpeg_decoder: force,
2232+
optimize_filesize: optimize,
2233+
}
2234+
.export_prepared(base, progress)
2235+
.await
22162236
}
2217-
.export(base, progress)
2218-
.await
22192237
}
22202238
}
22212239

0 commit comments

Comments
 (0)