Skip to content
Closed
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
37 changes: 34 additions & 3 deletions apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ impl ShowCapWindow {
.visible_on_all_workspaces(true)
.content_protected(should_protect)
.center()
.transparent(true)
.initialization_script(format!(
"
window.__CAP__ = window.__CAP__ ?? {{}};
Expand All @@ -275,9 +276,38 @@ impl ShowCapWindow {
))
.build()?;

if new_recording_flow {
#[cfg(target_os = "macos")]
crate::platform::set_window_level(window.as_ref().window(), 50);
#[cfg(target_os = "macos")]
{
window
.run_on_main_thread({
let win = window.as_ref().window();
move || unsafe {
let ns_win =
win.ns_window().unwrap() as *const objc2_app_kit::NSWindow;

// Hide traffic lights if needed
if let Some(button) = (*ns_win).standardWindowButton(
objc2_app_kit::NSWindowButton::CloseButton,
) {
button.setHidden(true);
}
if let Some(button) = (*ns_win).standardWindowButton(
objc2_app_kit::NSWindowButton::MiniaturizeButton,
) {
button.setHidden(true);
}
if let Some(button) = (*ns_win)
.standardWindowButton(objc2_app_kit::NSWindowButton::ZoomButton)
{
button.setHidden(true);
}
}
})
.ok();

if new_recording_flow {
crate::platform::set_window_level(window.as_ref().window(), 50);
}
}

#[cfg(target_os = "macos")]
Expand Down Expand Up @@ -398,6 +428,7 @@ impl ShowCapWindow {
.resizable(true)
.maximized(false)
.center()
.transparent(true)
.build()?
}
Self::Editor { .. } => {
Expand Down
148 changes: 148 additions & 0 deletions apps/desktop/src/icons.tsx

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions apps/desktop/src/routes/(window-chrome).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import { onCleanup, onMount, type ParentProps, Suspense } from "solid-js";
import { AbsoluteInsetLoader } from "~/components/Loader";
import CaptionControlsWindows11 from "~/components/titlebar/controls/CaptionControlsWindows11";
import { initializeTitlebar } from "~/utils/titlebar-state";
import {
useWindowChromeContext,
WindowChromeContext,
} from "./(window-chrome)/Context";
import { useWindowChromeContext, WindowChromeContext } from "./(window-chrome)/Context";

export const route = {
info: {
Expand All @@ -34,7 +31,18 @@ export default function (props: RouteSectionProps) {

return (
<WindowChromeContext>
<div class="flex overflow-hidden flex-col w-screen h-screen max-h-screen divide-y divide-gray-5 bg-gray-1">
<div
class="flex overflow-hidden flex-col w-screen h-screen max-h-screen p-1"
style={{
"border-radius": "20px",
border: "1px solid rgba(255, 255, 255, 0.10)",
background: "rgba(9, 10, 11, 1)",
// "box-shadow":
// "0 1px 1px -0.5px rgba(0, 0, 0, 0.16), 0 3px 3px -1.5px rgba(0, 0, 0, 0.16), 0 6px 6px -3px rgba(0, 0, 0, 0.16), 0 12px 12px -6px rgba(0, 0, 0, 0.16), 0 24px 24px -12px rgba(0, 0, 0, 0.16)",
// "backdrop-filter": "blur(15px)",
// "-webkit-backdrop-filter": "blur(15px)", // For Safari/WebKit
}}
>
<Header />

{/* breaks sometimes */}
Expand Down Expand Up @@ -79,10 +87,7 @@ function Header() {

return (
<header
class={cx(
"flex items-center space-x-1 h-9 select-none shrink-0 bg-gray-2",
isWindows ? "flex-row" : "flex-row-reverse pl-[4.2rem]",
)}
class={cx("flex items-center space-x-1 h-10 select-none shrink-0", isWindows ? "flex-row" : "flex-row")}
data-tauri-drag-region
>
{ctx.state()?.items}
Expand All @@ -96,9 +101,5 @@ function Inner(props: ParentProps) {
if (location.pathname !== "/") getCurrentWindow().show();
});

return (
<div class="flex overflow-y-hidden flex-col flex-1 animate-in fade-in">
{props.children}
</div>
);
return <div class="flex overflow-y-hidden flex-col flex-1 animate-in fade-in">{props.children}</div>;
}
28 changes: 11 additions & 17 deletions apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { CameraInfo } from "~/utils/tauri";
import InfoPill from "./InfoPill";
import TargetSelectInfoPill from "./TargetSelectInfoPill";
import useRequestPermission from "./useRequestPermission";
import { CameraIcon } from "~/icons";

const NO_CAMERA = "No Camera";

Expand All @@ -20,8 +21,8 @@ export default function CameraSelect(props: {
<CameraSelectBase
{...props}
PillComponent={InfoPill}
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
iconClass="text-gray-10 size-4"
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 cursor-pointer hover:bg-white/[0.03] disabled:text-gray-11 text-neutral-300 hover:text-white KSelect"
iconClass="size-4"
/>
);
}
Expand All @@ -31,23 +32,18 @@ export function CameraSelectBase(props: {
options: CameraInfo[];
value: CameraInfo | null;
onChange: (camera: CameraInfo | null) => void;
PillComponent: Component<
ComponentProps<"button"> & { variant: "blue" | "red" }
>;
PillComponent: Component<ComponentProps<"button"> & { variant: "blue" | "red" }>;
class: string;
iconClass: string;
}) {
const currentRecording = createCurrentRecordingQuery();
const permissions = createQuery(() => getPermissions);
const requestPermission = useRequestPermission();

const permissionGranted = () =>
permissions?.data?.camera === "granted" ||
permissions?.data?.camera === "notNeeded";
const permissionGranted = () => permissions?.data?.camera === "granted" || permissions?.data?.camera === "notNeeded";

const onChange = (cameraLabel: CameraInfo | null) => {
if (!cameraLabel && !permissionGranted())
return requestPermission("camera");
if (!cameraLabel && !permissionGranted()) return requestPermission("camera");

props.onChange(cameraLabel);

Expand Down Expand Up @@ -80,7 +76,7 @@ export function CameraSelectBase(props: {
text: o.display_name,
checked: o === props.value,
action: () => onChange(o),
}),
})
),
])
.then((items) => Menu.new({ items }))
Expand All @@ -90,11 +86,9 @@ export function CameraSelectBase(props: {
}}
class={props.class}
>
<IconCapCamera class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">
{props.value?.display_name ?? NO_CAMERA}
</p>
<TargetSelectInfoPill
<CameraIcon class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">{props.value?.display_name ?? NO_CAMERA}</p>
{/* <TargetSelectInfoPill
PillComponent={props.PillComponent}
value={props.value}
permissionGranted={permissionGranted()}
Expand All @@ -106,7 +100,7 @@ export function CameraSelectBase(props: {
props.onChange(null);
}
}}
/>
/> */}
</button>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { cx } from "cva";
import { type Component, type ComponentProps, splitProps } from "solid-js";

type HorizontalTargetButtonProps = {
selected: boolean;
Component: Component<ComponentProps<"svg">>;
name: string;
disabled?: boolean;
} & ComponentProps<"button">;

function HorizontalTargetButton(props: HorizontalTargetButtonProps) {
const [local, rest] = splitProps(props, ["selected", "Component", "name", "disabled", "class"]);

return (
<button
{...rest}
type="button"
disabled={local.disabled}
aria-pressed={local.selected ? "true" : "false"}
class={cx(
"flex w-full h-9 flex-row items-center gap-3 px-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-9 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-1",
local.selected ? "text-gray-12" : "text-gray-12 hover:bg-white/[0.08]",
local.disabled && "pointer-events-none opacity-60",
local.class
)}
style={{
"border-radius": "10px",
border: "0.5px solid rgba(255, 255, 255, 0.10)",
background: "rgba(255, 255, 255, 0.05)",
"box-shadow": "0 1px 1px -0.5px rgba(0, 0, 0, 0.16)",
}}
>
<local.Component
class={cx("size-5 transition-colors flex-shrink-0", local.selected ? "text-gray-12" : "text-gray-9")}
/>
<p class="text-sm font-medium">{local.name}</p>
</button>
);
}

export default HorizontalTargetButton;
18 changes: 13 additions & 5 deletions apps/desktop/src/routes/(window-chrome)/new-main/InfoPill.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { cx } from "cva";
import type { ComponentProps } from "solid-js";

export default function InfoPill(
props: ComponentProps<"button"> & { variant: "blue" | "red" },
) {
export default function InfoPill(props: ComponentProps<"button"> & { variant: "blue" | "red" }) {
return (
<button
{...props}
type="button"
class={cx("px-2 py-0.5 rounded-full text-white text-[11px]", props.variant === "blue" ? "bg-blue-9" : "bg-red-9")}
/>
);
}

export function InfoPillNew(props: ComponentProps<"button"> & { variant: "on" | "off" }) {
return (
<button
{...props}
type="button"
class={cx(
"px-2 py-0.5 rounded-full text-white text-[11px]",
props.variant === "blue" ? "bg-blue-9" : "bg-red-9",
"px-2 h-6 rounded-[6px] text-[11px] font-medium",
props.variant === "on" ? "bg-blue-9 text-white " : "bg-white/10 text-white/40"
)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { createQuery } from "@tanstack/solid-query";
import { CheckMenuItem, Menu, PredefinedMenuItem } from "@tauri-apps/api/menu";
import { cx } from "cva";
import {
type Component,
type ComponentProps,
createSignal,
onMount,
Show,
} from "solid-js";
import { type Component, type ComponentProps, createSignal, onMount, Show } from "solid-js";
import { trackEvent } from "~/utils/analytics";
import { createTauriEventListener } from "~/utils/createEventListener";
import { createCurrentRecordingQuery, getPermissions } from "~/utils/queries";
import { events } from "~/utils/tauri";
import InfoPill from "./InfoPill";
import TargetSelectInfoPill from "./TargetSelectInfoPill";
import useRequestPermission from "./useRequestPermission";
import { MicrophoneIcon } from "~/icons";

const NO_MICROPHONE = "No Microphone";

Expand All @@ -27,9 +22,9 @@ export default function MicrophoneSelect(props: {
return (
<MicrophoneSelectBase
{...props}
class="flex overflow-hidden relative z-10 flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 bg-gray-3 disabled:text-gray-11 KSelect"
class="flex flex-row gap-2 items-center px-2 w-full h-9 rounded-lg transition-colors cursor-default disabled:opacity-70 cursor-pointer hover:bg-white/[0.03] disabled:text-gray-11 text-neutral-300 hover:text-white KSelect"
levelIndicatorClass="bg-blue-7"
iconClass="text-gray-10 size-4"
iconClass="size-4"
PillComponent={InfoPill}
/>
);
Expand All @@ -43,9 +38,7 @@ export function MicrophoneSelectBase(props: {
class: string;
levelIndicatorClass: string;
iconClass: string;
PillComponent: Component<
ComponentProps<"button"> & { variant: "blue" | "red" }
>;
PillComponent: Component<ComponentProps<"button"> & { variant: "blue" | "red" }>;
}) {
const DB_SCALE = 40;

Expand All @@ -58,8 +51,7 @@ export function MicrophoneSelectBase(props: {
const requestPermission = useRequestPermission();

const permissionGranted = () =>
permissions?.data?.microphone === "granted" ||
permissions?.data?.microphone === "notNeeded";
permissions?.data?.microphone === "granted" || permissions?.data?.microphone === "notNeeded";

type Option = { name: string };

Expand All @@ -80,8 +72,7 @@ export function MicrophoneSelectBase(props: {
});

// visual audio level from 0 -> 1
const audioLevel = () =>
(1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE) ** 0.5;
const audioLevel = () => (1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE) ** 0.5;

// Initialize audio input if needed - only once when component mounts
onMount(() => {
Expand Down Expand Up @@ -116,7 +107,7 @@ export function MicrophoneSelectBase(props: {
text: name,
checked: name === props.value,
action: () => handleMicrophoneChange({ name: name }),
}),
})
),
])
.then((items) => Menu.new({ items }))
Expand All @@ -130,17 +121,15 @@ export function MicrophoneSelectBase(props: {
<div
class={cx(
"opacity-50 left-0 inset-y-0 absolute transition-[right] duration-100 -z-10",
props.levelIndicatorClass,
props.levelIndicatorClass
)}
style={{ right: `${audioLevel() * 100}%` }}
/>
)}
</Show>
<IconCapMicrophone class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">
{props.value ?? NO_MICROPHONE}
</p>
<TargetSelectInfoPill
<MicrophoneIcon class={props.iconClass} />
<p class="flex-1 text-sm text-left truncate">{props.value ?? NO_MICROPHONE}</p>
{/* <TargetSelectInfoPill
PillComponent={props.PillComponent}
value={props.value}
permissionGranted={permissionGranted()}
Expand All @@ -151,7 +140,7 @@ export function MicrophoneSelectBase(props: {
props.onChange(null);
}
}}
/>
/> */}
</button>
</div>
);
Expand Down
Loading