Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/desktop/src-tauri/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ impl CameraPreviewManager {
self.preview.is_some()
}

pub fn notify_window_resized(&self, width: u32, height: u32) {
if let Some(preview) = &self.preview {
preview
.reconfigure
.send(ReconfigureEvent::WindowResized { width, height })
.map_err(|err| error!("Error notifying camera preview of resize: {err}"))
.ok();
}
}

/// Initialize the camera preview for a specific Tauri window
pub async fn init_window(
&mut self,
Expand Down Expand Up @@ -169,6 +179,7 @@ impl CameraPreviewManager {
#[derive(Clone)]
enum ReconfigureEvent {
State(CameraPreviewState),
WindowResized { width: u32, height: u32 },
Shutdown,
}

Expand Down Expand Up @@ -604,6 +615,10 @@ impl Renderer {
self.reconfigure_gpu_surface(width, height);
}
}
Err(ReconfigureEvent::WindowResized { width, height }) => {
trace!("CameraPreview/ReconfigureEvent.WindowResized({width}x{height})");
self.reconfigure_gpu_surface(width, height);
}
Err(ReconfigureEvent::Shutdown) => return,
}
}
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src-tauri/src/captions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,10 @@ pub async fn save_captions(
serde_json::Number::from_f64(settings.word_transition_duration as f64).unwrap(),
),
);
settings_obj.insert(
"activeWordHighlight".to_string(),
serde_json::Value::Bool(settings.active_word_highlight),
);

json_obj.insert(
"settings".to_string(),
Expand Down Expand Up @@ -1200,6 +1204,12 @@ pub fn parse_captions_json(json: &str) -> Result<cap_project::CaptionsData, Stri
.and_then(|v| v.as_f64())
.unwrap_or(0.25) as f32;

let active_word_highlight = settings_obj
.get("activeWordHighlight")
.or_else(|| settings_obj.get("active_word_highlight"))
.and_then(|v| v.as_bool())
.unwrap_or(false);

cap_project::CaptionSettings {
enabled,
font,
Expand All @@ -1217,6 +1227,7 @@ pub fn parse_captions_json(json: &str) -> Result<cap_project::CaptionsData, Stri
fade_duration,
linger_duration,
word_transition_duration,
active_word_highlight,
}
} else {
cap_project::CaptionSettings::default()
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2932,11 +2932,15 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
match event {
WindowEvent::CloseRequested { .. } => {
if let Ok(CapWindowId::Camera) = CapWindowId::from_str(label) {
tracing::warn!("Camera window CloseRequested event received!");
tokio::spawn(cleanup_camera_window(app.clone()));
}
}
WindowEvent::Destroyed => {
if let Ok(window_id) = CapWindowId::from_str(label) {
if matches!(window_id, CapWindowId::Camera) {
tracing::warn!("Camera window Destroyed event received!");
}
match window_id {
CapWindowId::Main => {
let app = app.clone();
Expand Down
19 changes: 19 additions & 0 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,29 @@ pub async fn start_recording(
win.close().ok();
}
}
#[cfg(windows)]
let had_camera_window = CapWindowId::Camera.get(&app).is_some();
#[cfg(windows)]
if had_camera_window {
tracing::info!(
"Closing camera window BEFORE InProgressRecording show (will recreate after)"
);
if let Some(cam_win) = CapWindowId::Camera.get(&app) {
cam_win.close().ok();
}
}

let _ = ShowCapWindow::InProgressRecording { countdown }
.show(&app)
.await;

#[cfg(windows)]
if had_camera_window {
tracing::info!("Recreating camera window after InProgressRecording");
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
ShowCapWindow::Camera.show(&app).await.ok();
}

if let Some(window) = CapWindowId::Main.get(&app) {
let _ = general_settings
.map(|v| v.main_window_recording_start_behaviour)
Expand Down
19 changes: 16 additions & 3 deletions apps/desktop/src-tauri/src/target_select_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use base64::prelude::*;
use cap_recording::screen_capture::ScreenCaptureTarget;

use crate::{
general_settings,
App, ArcLock, general_settings,
window_exclusion::WindowExclusion,
windows::{CapWindowId, ShowCapWindow},
};
Expand Down Expand Up @@ -163,10 +163,13 @@ pub async fn update_camera_overlay_bounds(
.get_webview_window("camera")
.ok_or("Camera window not found")?;

let width_u32 = width as u32;
let height_u32 = height as u32;

window
.set_size(tauri::Size::Physical(tauri::PhysicalSize {
width: width as u32,
height: height as u32,
width: width_u32,
height: height_u32,
}))
.map_err(|e| e.to_string())?;
window
Expand All @@ -176,6 +179,16 @@ pub async fn update_camera_overlay_bounds(
}))
.map_err(|e| e.to_string())?;

let scale_factor = window.scale_factor().unwrap_or(1.0);
let logical_width = (width / scale_factor) as u32;
let logical_height = (height / scale_factor) as u32;

let state = app.state::<ArcLock<App>>();
let app_state = state.read().await;
app_state
.camera_preview
.notify_window_resized(logical_width, logical_height);

Ok(())
}

Expand Down
19 changes: 6 additions & 13 deletions apps/desktop/src/routes/camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,16 +589,6 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {

let cameraCanvasRef: HTMLCanvasElement | undefined;

createEffect(
on(
() => rawOptions.cameraLabel,
(label) => {
if (label === null) getCurrentWindow().close();
},
{ defer: true },
),
);

onMount(() => getCurrentWindow().show());

return (
Expand Down Expand Up @@ -700,15 +690,18 @@ function Canvas(props: {
latestFrame: Accessor<{ width: number; data: ImageData } | null | undefined>;
state: CameraWindowState;
ref: HTMLCanvasElement | undefined;
containerSize?: { width: number; height: number };
}) {
const style = () => {
const frame = props.latestFrame();
if (!frame) return {};

const aspectRatio = frame.data.width / frame.data.height;

// Use state.size directly for immediate feedback
const base = props.state.size;
// Use container size if available (for external resize), otherwise use state.size
const base = props.containerSize
? Math.min(props.containerSize.width, props.containerSize.height)
: props.state.size;

// Replicate window size logic synchronously for the canvas
const winWidth =
Expand Down Expand Up @@ -741,7 +734,7 @@ function Canvas(props: {
else
return {
width: base,
height: base * aspectRatio,
height: base / aspectRatio,
};
})();

Expand Down
41 changes: 34 additions & 7 deletions apps/desktop/src/routes/editor/CaptionsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ function RgbInput(props: { value: string; onChange: (value: string) => void }) {
}

export function CaptionsTab() {
const { project, setProject, editorInstance, editorState } =
const { project, setProject, editorInstance, editorState, setEditorState } =
useEditorContext();

const getSetting = <K extends keyof CaptionSettings>(
Expand All @@ -172,12 +172,18 @@ export function CaptionsTab() {
const [selectedLanguage, setSelectedLanguage] = createSignal("auto");
const [downloadedModels, setDownloadedModels] = createSignal<string[]>([]);

const [isDownloading, setIsDownloading] = createSignal(false);
const [downloadProgress, setDownloadProgress] = createSignal(0);
const [downloadingModel, setDownloadingModel] = createSignal<string | null>(
null,
);
const [isGenerating, setIsGenerating] = createSignal(false);
const isDownloading = () => editorState.captions.isDownloading;
const setIsDownloading = (value: boolean) =>
setEditorState("captions", "isDownloading", value);
const downloadProgress = () => editorState.captions.downloadProgress;
const setDownloadProgress = (value: number) =>
setEditorState("captions", "downloadProgress", value);
const downloadingModel = () => editorState.captions.downloadingModel;
const setDownloadingModel = (value: string | null) =>
setEditorState("captions", "downloadingModel", value);
const isGenerating = () => editorState.captions.isGenerating;
const setIsGenerating = (value: boolean) =>
setEditorState("captions", "isGenerating", value);
const [hasAudio, setHasAudio] = createSignal(false);

createEffect(
Expand Down Expand Up @@ -662,6 +668,27 @@ export function CaptionsTab() {
/>
</div>

<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-gray-11 text-sm">
Active Word Highlight
</span>
<Toggle
checked={getSetting("activeWordHighlight")}
onChange={(checked) =>
updateCaptionSetting("activeWordHighlight", checked)
}
disabled={!hasCaptions()}
/>
</div>
<p class="text-xs text-gray-10">
This is the first version of captions in Cap. Active word
highlighting may be inaccurate in some situations. We're
working on a fix for this and it will be released in
upcoming versions.
</p>
</div>

<div class="flex flex-col gap-2">
<span class="text-gray-11 text-sm">Font Color</span>
<RgbInput
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/routes/editor/ExportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,10 @@ export function ExportPage() {
<Button
variant="gray"
class="mt-4 hover:underline"
onClick={() => setExportState({ type: "idle" })}
onClick={() => {
setExportState({ type: "idle" });
handleBack();
}}
>
<IconLucideArrowLeft class="size-4" />
Back to Editor
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/src/routes/editor/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,12 @@ export const [EditorContextProvider, useEditorContext] = createContextProvider(
previewTime: null as number | null,
playbackTime: 0,
playing: false,
captions: {
isGenerating: false,
isDownloading: false,
downloadProgress: 0,
downloadingModel: null as string | null,
},
timeline: {
interactMode: "seek" as "seek" | "split",
selection: null as
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/src/routes/screenshot-editor/AnnotationLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function AnnotationLayer(props: {
createEffect(() => {
const rect = props.imageRect;
if (rect.width <= 0 || rect.height <= 0) return;
const masksToRemove: string[] = [];
for (const ann of annotations) {
if (ann.type !== "mask") continue;
const left = clampValue(
Expand All @@ -110,6 +111,10 @@ export function AnnotationLayer(props: {
);
const width = Math.max(0, right - left);
const height = Math.max(0, bottom - top);
if (width < 5 || height < 5) {
masksToRemove.push(ann.id);
continue;
}
if (
left !== Math.min(ann.x, ann.x + ann.width) ||
top !== Math.min(ann.y, ann.y + ann.height) ||
Expand All @@ -124,6 +129,11 @@ export function AnnotationLayer(props: {
});
}
}
if (masksToRemove.length > 0) {
setAnnotations((prev) =>
prev.filter((a) => !masksToRemove.includes(a.id)),
);
}
});

// Helper to get coordinates in SVG space
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/routes/screenshot-editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ function Dialogs() {
contentClass=""
open={dialog().open}
onOpenChange={(o) => {
if (!o) setDialog((d) => ({ ...d, open: false }));
if (!o) setDialog({ open: false });
}}
>
<Show
Expand Down Expand Up @@ -434,7 +434,7 @@ function Dialogs() {
y: bounds.height,
},
});
setDialog((d) => ({ ...d, open: false }));
setDialog({ open: false });
}}
>
Save
Expand Down
Loading