Merge pull request #613 from auberginewly/feat/zoom-hold-preview
feat(zoom): hold-to-preview button for zoom focus editing (prototype for #612)
This commit is contained in:
@@ -238,6 +238,8 @@ interface SettingsPanelProps {
|
|||||||
selectedZoomCustomScale?: number | null;
|
selectedZoomCustomScale?: number | null;
|
||||||
onZoomCustomScaleChange?: (scale: number) => void;
|
onZoomCustomScaleChange?: (scale: number) => void;
|
||||||
onZoomCustomScaleCommit?: () => void;
|
onZoomCustomScaleCommit?: () => void;
|
||||||
|
onZoomPreviewStart?: () => void;
|
||||||
|
onZoomPreviewEnd?: () => void;
|
||||||
selectedZoomFocusMode?: ZoomFocusMode | null;
|
selectedZoomFocusMode?: ZoomFocusMode | null;
|
||||||
onZoomFocusModeChange?: (mode: ZoomFocusMode) => void;
|
onZoomFocusModeChange?: (mode: ZoomFocusMode) => void;
|
||||||
selectedZoomFocus?: ZoomFocus | null;
|
selectedZoomFocus?: ZoomFocus | null;
|
||||||
@@ -367,6 +369,8 @@ export function SettingsPanel({
|
|||||||
selectedZoomCustomScale,
|
selectedZoomCustomScale,
|
||||||
onZoomCustomScaleChange,
|
onZoomCustomScaleChange,
|
||||||
onZoomCustomScaleCommit,
|
onZoomCustomScaleCommit,
|
||||||
|
onZoomPreviewStart,
|
||||||
|
onZoomPreviewEnd,
|
||||||
selectedZoomFocusMode,
|
selectedZoomFocusMode,
|
||||||
onZoomFocusModeChange,
|
onZoomFocusModeChange,
|
||||||
selectedZoomFocus,
|
selectedZoomFocus,
|
||||||
@@ -949,6 +953,31 @@ export function SettingsPanel({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{zoomEnabled && onZoomPreviewStart && onZoomPreviewEnd && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onPointerDown={() => onZoomPreviewStart()}
|
||||||
|
onPointerUp={() => onZoomPreviewEnd()}
|
||||||
|
onPointerLeave={() => onZoomPreviewEnd()}
|
||||||
|
onPointerCancel={() => onZoomPreviewEnd()}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if ((e.key === " " || e.key === "Enter") && !e.repeat) {
|
||||||
|
e.preventDefault();
|
||||||
|
onZoomPreviewStart();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyUp={(e) => {
|
||||||
|
if (e.key === " " || e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
onZoomPreviewEnd();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => onZoomPreviewEnd()}
|
||||||
|
className="h-7 w-full select-none rounded-md border border-white/[0.08] bg-white/[0.04] text-[10px] font-semibold text-slate-300 transition-all duration-150 ease-out hover:bg-white/[0.08] hover:text-slate-100 active:border-[#34B27B]/50 active:bg-[#34B27B] active:text-white cursor-pointer"
|
||||||
|
>
|
||||||
|
{t("zoom.previewHold")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{zoomEnabled &&
|
{zoomEnabled &&
|
||||||
selectedZoomFocusMode !== "auto" &&
|
selectedZoomFocusMode !== "auto" &&
|
||||||
selectedZoomFocus &&
|
selectedZoomFocus &&
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ export default function VideoEditor() {
|
|||||||
const durationRef = useRef(duration);
|
const durationRef = useRef(duration);
|
||||||
durationRef.current = duration;
|
durationRef.current = duration;
|
||||||
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
|
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
|
||||||
|
const [isPreviewingZoom, setIsPreviewingZoom] = useState(false);
|
||||||
const [selectedTrimId, setSelectedTrimId] = useState<string | null>(null);
|
const [selectedTrimId, setSelectedTrimId] = useState<string | null>(null);
|
||||||
const [selectedSpeedId, setSelectedSpeedId] = useState<string | null>(null);
|
const [selectedSpeedId, setSelectedSpeedId] = useState<string | null>(null);
|
||||||
const [selectedAnnotationId, setSelectedAnnotationId] = useState<string | null>(null);
|
const [selectedAnnotationId, setSelectedAnnotationId] = useState<string | null>(null);
|
||||||
@@ -2120,6 +2121,7 @@ export default function VideoEditor() {
|
|||||||
cursorMotionBlur={cursorMotionBlur}
|
cursorMotionBlur={cursorMotionBlur}
|
||||||
cursorClickBounce={cursorClickBounce}
|
cursorClickBounce={cursorClickBounce}
|
||||||
cursorClipToBounds={cursorClipToBounds}
|
cursorClipToBounds={cursorClipToBounds}
|
||||||
|
isPreviewingZoom={isPreviewingZoom}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2155,6 +2157,8 @@ export default function VideoEditor() {
|
|||||||
}
|
}
|
||||||
onZoomCustomScaleChange={handleZoomCustomScaleChange}
|
onZoomCustomScaleChange={handleZoomCustomScaleChange}
|
||||||
onZoomCustomScaleCommit={handleZoomCustomScaleCommit}
|
onZoomCustomScaleCommit={handleZoomCustomScaleCommit}
|
||||||
|
onZoomPreviewStart={() => setIsPreviewingZoom(true)}
|
||||||
|
onZoomPreviewEnd={() => setIsPreviewingZoom(false)}
|
||||||
selectedZoomFocusMode={
|
selectedZoomFocusMode={
|
||||||
selectedZoomId
|
selectedZoomId
|
||||||
? (zoomRegions.find((z) => z.id === selectedZoomId)?.focusMode ?? "manual")
|
? (zoomRegions.find((z) => z.id === selectedZoomId)?.focusMode ?? "manual")
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ interface VideoPlaybackProps {
|
|||||||
cursorMotionBlur?: number;
|
cursorMotionBlur?: number;
|
||||||
cursorClickBounce?: number;
|
cursorClickBounce?: number;
|
||||||
cursorClipToBounds?: boolean;
|
cursorClipToBounds?: boolean;
|
||||||
|
// When true, render the selected zoom at the playhead even while paused —
|
||||||
|
// lets the editor preview the zoom effect without leaving the focus-edit view.
|
||||||
|
isPreviewingZoom?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoPlaybackRef {
|
export interface VideoPlaybackRef {
|
||||||
@@ -271,6 +274,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
|||||||
cursorMotionBlur = DEFAULT_CURSOR_SETTINGS.motionBlur,
|
cursorMotionBlur = DEFAULT_CURSOR_SETTINGS.motionBlur,
|
||||||
cursorClickBounce = DEFAULT_CURSOR_SETTINGS.clickBounce,
|
cursorClickBounce = DEFAULT_CURSOR_SETTINGS.clickBounce,
|
||||||
cursorClipToBounds = DEFAULT_CURSOR_SETTINGS.clipToBounds,
|
cursorClipToBounds = DEFAULT_CURSOR_SETTINGS.clipToBounds,
|
||||||
|
isPreviewingZoom = false,
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
@@ -342,6 +346,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
|||||||
const cursorMotionBlurRef = useRef(cursorMotionBlur);
|
const cursorMotionBlurRef = useRef(cursorMotionBlur);
|
||||||
const cursorClickBounceRef = useRef(cursorClickBounce);
|
const cursorClickBounceRef = useRef(cursorClickBounce);
|
||||||
const cursorClipToBoundsRef = useRef(cursorClipToBounds);
|
const cursorClipToBoundsRef = useRef(cursorClipToBounds);
|
||||||
|
const isPreviewingZoomRef = useRef(isPreviewingZoom);
|
||||||
const motionBlurStateRef = useRef<MotionBlurState>(createMotionBlurState());
|
const motionBlurStateRef = useRef<MotionBlurState>(createMotionBlurState());
|
||||||
const onTimeUpdateRef = useRef(onTimeUpdate);
|
const onTimeUpdateRef = useRef(onTimeUpdate);
|
||||||
const onPlayStateChangeRef = useRef(onPlayStateChange);
|
const onPlayStateChangeRef = useRef(onPlayStateChange);
|
||||||
@@ -833,6 +838,10 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
|||||||
cursorClipToBoundsRef.current = cursorClipToBounds;
|
cursorClipToBoundsRef.current = cursorClipToBounds;
|
||||||
}, [cursorClipToBounds]);
|
}, [cursorClipToBounds]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isPreviewingZoomRef.current = isPreviewingZoom;
|
||||||
|
}, [isPreviewingZoom]);
|
||||||
|
|
||||||
// Sync cursor overlay config when settings change
|
// Sync cursor overlay config when settings change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const overlay = cursorOverlayRef.current;
|
const overlay = cursorOverlayRef.current;
|
||||||
@@ -1310,7 +1319,8 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
|||||||
// If a zoom is selected but video is not playing, show default unzoomed view
|
// If a zoom is selected but video is not playing, show default unzoomed view
|
||||||
const selectedId = selectedZoomIdRef.current;
|
const selectedId = selectedZoomIdRef.current;
|
||||||
const hasSelectedZoom = selectedId !== null;
|
const hasSelectedZoom = selectedId !== null;
|
||||||
const shouldShowUnzoomedView = hasSelectedZoom && !isPlayingRef.current;
|
const shouldShowUnzoomedView =
|
||||||
|
hasSelectedZoom && !isPlayingRef.current && !isPreviewingZoomRef.current;
|
||||||
|
|
||||||
if (region && strength > 0 && !shouldShowUnzoomedView) {
|
if (region && strength > 0 && !shouldShowUnzoomedView) {
|
||||||
const zoomScale = blendedScale ?? ZOOM_DEPTH_SCALES[region.depth];
|
const zoomScale = blendedScale ?? ZOOM_DEPTH_SCALES[region.depth];
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "اضغط مع الاستمرار لمعاينة تأثير التكبير",
|
||||||
"level": "مستوى التكبير",
|
"level": "مستوى التكبير",
|
||||||
"selectRegion": "حدد منطقة التكبير للتعديل",
|
"selectRegion": "حدد منطقة التكبير للتعديل",
|
||||||
"deleteZoom": "حذف التكبير",
|
"deleteZoom": "حذف التكبير",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "Hold to preview zoom effect",
|
||||||
"level": "Zoom Level",
|
"level": "Zoom Level",
|
||||||
"customScale": "Custom Zoom",
|
"customScale": "Custom Zoom",
|
||||||
"selectRegion": "Select a zoom region to adjust",
|
"selectRegion": "Select a zoom region to adjust",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "Mantener para previsualizar el efecto de zoom",
|
||||||
"level": "Nivel de zoom",
|
"level": "Nivel de zoom",
|
||||||
"selectRegion": "Selecciona una región de zoom para ajustar",
|
"selectRegion": "Selecciona una región de zoom para ajustar",
|
||||||
"deleteZoom": "Eliminar zoom",
|
"deleteZoom": "Eliminar zoom",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "Maintenir pour prévisualiser l’effet de zoom",
|
||||||
"level": "Niveau de zoom",
|
"level": "Niveau de zoom",
|
||||||
"selectRegion": "Sélectionnez une région de zoom à ajuster",
|
"selectRegion": "Sélectionnez une région de zoom à ajuster",
|
||||||
"deleteZoom": "Supprimer le zoom",
|
"deleteZoom": "Supprimer le zoom",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "押している間ズーム効果をプレビュー",
|
||||||
"level": "ズーム倍率",
|
"level": "ズーム倍率",
|
||||||
"selectRegion": "ズーム範囲を選択して調整",
|
"selectRegion": "ズーム範囲を選択して調整",
|
||||||
"deleteZoom": "ズームを削除",
|
"deleteZoom": "ズームを削除",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "누르고 있으면 줌 효과 미리보기",
|
||||||
"level": "줌 레벨",
|
"level": "줌 레벨",
|
||||||
"customScale": "커스텀 줌",
|
"customScale": "커스텀 줌",
|
||||||
"selectRegion": "조정할 줌 구간을 선택하세요",
|
"selectRegion": "조정할 줌 구간을 선택하세요",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "Удерживайте для предпросмотра эффекта зума",
|
||||||
"level": "Уровень масштабирования",
|
"level": "Уровень масштабирования",
|
||||||
"selectRegion": "Выберите область масштабирования для настройки",
|
"selectRegion": "Выберите область масштабирования для настройки",
|
||||||
"deleteZoom": "Удалить масштабирование",
|
"deleteZoom": "Удалить масштабирование",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "Yakınlaştırma efektini önizlemek için basılı tutun",
|
||||||
"level": "Yakınlaştırma Seviyesi",
|
"level": "Yakınlaştırma Seviyesi",
|
||||||
"selectRegion": "Ayarlamak için bir yakınlaştırma bölgesi seçin",
|
"selectRegion": "Ayarlamak için bir yakınlaştırma bölgesi seçin",
|
||||||
"deleteZoom": "Yakınlaştırmayı Sil",
|
"deleteZoom": "Yakınlaştırmayı Sil",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "Giữ để xem trước hiệu ứng phóng to",
|
||||||
"level": "Mức độ thu phóng",
|
"level": "Mức độ thu phóng",
|
||||||
"selectRegion": "Chọn vùng thu phóng để điều chỉnh",
|
"selectRegion": "Chọn vùng thu phóng để điều chỉnh",
|
||||||
"deleteZoom": "Xóa thu phóng",
|
"deleteZoom": "Xóa thu phóng",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "按住预览放大效果",
|
||||||
"level": "缩放级别",
|
"level": "缩放级别",
|
||||||
"selectRegion": "选择要调整的缩放区域",
|
"selectRegion": "选择要调整的缩放区域",
|
||||||
"deleteZoom": "删除缩放",
|
"deleteZoom": "删除缩放",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"zoom": {
|
"zoom": {
|
||||||
|
"previewHold": "按住預覽放大效果",
|
||||||
"level": "縮放級別",
|
"level": "縮放級別",
|
||||||
"selectRegion": "選擇要調整的縮放區域",
|
"selectRegion": "選擇要調整的縮放區域",
|
||||||
"deleteZoom": "刪除縮放",
|
"deleteZoom": "刪除縮放",
|
||||||
|
|||||||
Reference in New Issue
Block a user