diff --git a/app/components/reusable/ButtonDropdown/ButtonDropdown.tsx b/app/components/reusable/ButtonDropdown/ButtonDropdown.tsx index be6e7d975d..6c87669ca6 100644 --- a/app/components/reusable/ButtonDropdown/ButtonDropdown.tsx +++ b/app/components/reusable/ButtonDropdown/ButtonDropdown.tsx @@ -1,4 +1,4 @@ -import { Fragment, useState } from "react"; +import { ButtonHTMLAttributes, Fragment, useState } from "react"; import { Menu, Transition } from "@headlessui/react"; import { IconDefinition, @@ -6,14 +6,20 @@ import { } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, ButtonProps, TransitionDialogue } from "~/components"; +import { twMerge } from "tailwind-merge"; type UnionedButtonProps = { label?: string } & ButtonProps; -interface ButtonDropdownProps { +interface ButtonDropdownProps extends ButtonHTMLAttributes { label: string; icon?: IconDefinition; + align?: "left" | "right"; + showSelected?: boolean; options: Array<{ label: string; + className?: string; + icon?: IconDefinition; + selected?: boolean; description?: string; onClick?: () => void; disabled?: boolean; @@ -32,9 +38,12 @@ interface ButtonDropdownProps { } export const ButtonDropdown = ({ + className, label, options, icon, + align = "left", + showSelected, }: ButtonDropdownProps) => { const [isOpen, setIsOpen] = useState(false); const [selectedOptionIndex, setSelectedOptionIndex] = useState( @@ -69,7 +78,12 @@ export const ButtonDropdown = ({
- @@ -85,7 +99,10 @@ export const ButtonDropdown = ({ >
{options.map((option, index) => ( @@ -97,16 +114,51 @@ export const ButtonDropdown = ({ {({ active }) => ( )} diff --git a/app/components/reusable/ButtonIcon/ButtonIcon.tsx b/app/components/reusable/ButtonIcon/ButtonIcon.tsx index 77c010a9a6..15c976b5d6 100644 --- a/app/components/reusable/ButtonIcon/ButtonIcon.tsx +++ b/app/components/reusable/ButtonIcon/ButtonIcon.tsx @@ -9,7 +9,7 @@ interface ButtonIconProps extends FontAwesomeIconProps { icon: IconDefinition; onClick: () => void; className?: string; - hoverFill?: boolean; + bgFill?: boolean; disabled?: boolean; } @@ -18,13 +18,13 @@ export const ButtonIcon = ({ size, onClick, className: propsClassName, - hoverFill = false, + bgFill = false, disabled, ...rest }: ButtonIconProps) => { const className = twMerge( - "flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150", - hoverFill + "box-content flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150", + bgFill ? "bg-ui-controls-button hover:bg-ui-controls-button/[0.75]" : "bg-transparent hover:bg-ui-panel/[0.4]", disabled && "opacity-50 hover:bg-transparent", @@ -33,7 +33,7 @@ export const ButtonIcon = ({ return ( ); }; diff --git a/app/components/signaled/Toaster.tsx b/app/components/signaled/Toaster.tsx index 4fc2415873..cf7f8579e6 100644 --- a/app/components/signaled/Toaster.tsx +++ b/app/components/signaled/Toaster.tsx @@ -9,6 +9,11 @@ import { ToastTypes } from "~/enums"; import { toasts, deleteToast } from "~/signals"; import { useEffect, useRef, useState } from "react"; +export type ToastDataType = { + type: ToastTypes; + message: string; +}; + export const Toaster = () => { useSignals(); const TITLES = { diff --git a/app/pages/PageEnigma/Editor/editor.ts b/app/pages/PageEnigma/Editor/editor.ts index d21baa0c45..57cef8327c 100644 --- a/app/pages/PageEnigma/Editor/editor.ts +++ b/app/pages/PageEnigma/Editor/editor.ts @@ -18,7 +18,11 @@ import { TimeLine } from "./timeline.js"; import { LipSyncEngine } from "./lip_sync_engine.js"; import { AnimationEngine } from "./animation_engine.js"; import { PointerLockControls } from "three/addons/controls/PointerLockControls.js"; -import { ClipGroup, EditorStates } from "~/pages/PageEnigma/enums"; +import { + ClipGroup, + EditorStates, + CameraAspectRatio, +} from "~/pages/PageEnigma/enums"; import { AssetType } from "~/enums"; import { XYZ } from "../datastructures/common"; import Queue from "~/pages/PageEnigma/Queue/Queue"; @@ -52,6 +56,8 @@ class Editor { activeScene: Scene; camera: THREE.PerspectiveCamera | null = null; render_camera: THREE.PerspectiveCamera | null = null; + render_camera_aspect_ratio: CameraAspectRatio = + CameraAspectRatio.HORIZONTAL_16_9; renderer: THREE.WebGLRenderer | undefined; rawRenderer: THREE.WebGLRenderer | undefined; clock: THREE.Clock | undefined; @@ -135,6 +141,7 @@ class Editor { save_manager: SaveManager; generation_options: GenerationOptions; + constructor() { console.log( "If you see this message twice! then it rendered twice, if you see it once it's all good.", @@ -184,8 +191,9 @@ class Editor { this.selectedCanvas = false; // Audio Engine Test. - this.render_width = 1280; - this.render_height = 720; + this.render_camera_aspect_ratio = CameraAspectRatio.HORIZONTAL_16_9; + this.render_width = this.getRenderDimensions().width; + this.render_height = this.getRenderDimensions().height; this.audio_engine = new AudioEngine(); this.emotion_engine = new EmotionEngine(this.version); @@ -230,7 +238,32 @@ class Editor { lipSync: false, }; } - + getRenderDimensions() { + switch (this.render_camera_aspect_ratio) { + case CameraAspectRatio.HORIZONTAL_16_9: { + return { + width: 1280, + height: 720, + aspectRatio: 16 / 9, + }; + } + case CameraAspectRatio.VERTICAL_9_16: { + return { + width: 720, + height: 1280, + aspectRatio: 9 / 16, + }; + } + case CameraAspectRatio.SQUARE_1_1: + default: { + return { + width: 1080, + height: 1080, + aspectRatio: 1, + }; + } + } + } isEmpty(value: string | null) { return value === null || value.trim().length === 0; } @@ -266,6 +299,23 @@ class Editor { updateCamViewCanvas(newCanvas: HTMLCanvasElement) { this.canvasRenderCamReference = newCanvas; } + + changeRenderCameraAspectRatio(newAspectRatio: CameraAspectRatio) { + this.render_camera_aspect_ratio = newAspectRatio; + const { width, height, aspectRatio } = this.getRenderDimensions(); + this.render_width = width; + this.render_height = height; + if (this.render_camera) { + this.render_camera.aspect = aspectRatio; + this.render_camera.updateProjectionMatrix(); + } + + Queue.publish({ + queueName: QueueNames.FROM_ENGINE, + action: fromEngineActions.CAMERA_ASPECT_RATIO_CHANGED, + data: this.render_camera_aspect_ratio, + }); + } initialize({ sceneToken, editorCanvasEl, @@ -397,6 +447,8 @@ class Editor { } this.control.addEventListener("change", this.renderScene.bind(this)); this.control.addEventListener("dragging-changed", (event: any) => { + //TODO: any should be the following + // (event: THREE.Event<"dragging-changed", TransformControls>) => { if (this.orbitControls == undefined) { return; } @@ -683,8 +735,23 @@ class Editor { if (this.rendering && this.rawRenderer && this.clock && this.renderer) { if (this.recorder === undefined && this.render_camera) { - this.rawRenderer.setSize(1024, 576); - this.render_camera.aspect = 1024 / 576; + const width = + this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 + ? 1024 + : this.render_camera_aspect_ratio === + CameraAspectRatio.VERTICAL_9_16 + ? 576 + : 1000; + const height = + this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 + ? 576 + : this.render_camera_aspect_ratio === + CameraAspectRatio.VERTICAL_9_16 + ? 1024 + : 1000; + + this.rawRenderer.setSize(width, height); + this.render_camera.aspect = width / height; } this.render_timer += this.clock.getDelta(); @@ -950,8 +1017,7 @@ class Editor { return; } - //this.renderer.setSize(this.render_width, this.render_height); - this.render_camera.aspect = this.render_width / this.render_height; + this.render_camera.aspect = this.getRenderDimensions().aspectRatio; this.render_camera.updateProjectionMatrix(); } diff --git a/app/pages/PageEnigma/Editor/timeline.ts b/app/pages/PageEnigma/Editor/timeline.ts index 801f47218c..b8a071b1fa 100644 --- a/app/pages/PageEnigma/Editor/timeline.ts +++ b/app/pages/PageEnigma/Editor/timeline.ts @@ -16,7 +16,11 @@ import { QueueNames } from "../Queue/QueueNames"; import { toEngineActions } from "../Queue/toEngineActions"; import { fromEngineActions } from "../Queue/fromEngineActions"; import { AssetType } from "~/enums"; -import { ClipGroup, ClipType } from "~/pages/PageEnigma/enums"; +import { + CameraAspectRatio, + ClipGroup, + ClipType, +} from "~/pages/PageEnigma/enums"; import { Keyframe, MediaItem, UpdateTime } from "~/pages/PageEnigma/models"; import Editor from "~/pages/PageEnigma/Editor/editor"; import EmotionEngine from "./emotion_engine"; @@ -179,6 +183,12 @@ export class TimeLine { this.editorEngine.generateVideo(); break; } + case toEngineActions.CHANGE_CAMERA_ASPECT_RATIO: { + this.editorEngine.changeRenderCameraAspectRatio( + data.data as CameraAspectRatio, + ); + break; + } default: console.log("Action Not Wired", action); } diff --git a/app/pages/PageEnigma/Editor/video_generation.ts b/app/pages/PageEnigma/Editor/video_generation.ts index cd48e6bd32..d6222f6a26 100644 --- a/app/pages/PageEnigma/Editor/video_generation.ts +++ b/app/pages/PageEnigma/Editor/video_generation.ts @@ -211,8 +211,8 @@ export class VideoGeneration { "anullsrc", // This adds a silent audio track "-max_muxing_queue_size", "999999", - "-vf", - "select=gte(n\\,1),scale=1024:576", // scale=1024:576 + // "-vf", + // "select=gte(n\\,1),scale=1024:576", // scale=1024:576 "-c:v", "libx264", // Specify video codec (optional, but recommended for MP4) "-c:a", diff --git a/app/pages/PageEnigma/PageEditor.tsx b/app/pages/PageEnigma/PageEditor.tsx index ddcdf20d5f..fe2beb03f9 100644 --- a/app/pages/PageEnigma/PageEditor.tsx +++ b/app/pages/PageEnigma/PageEditor.tsx @@ -20,6 +20,7 @@ import { } from "~/pages/PageEnigma/signals"; import { EditorCanvas } from "./comps/EngineCanvases"; import { SceneContainer } from "./comps/SceneContainer"; +import { AspectRatioMenu } from "./comps/AspectRatioMenu"; export const PageEditor = () => { useSignals(); @@ -80,6 +81,7 @@ export const PageEditor = () => {
+
diff --git a/app/pages/PageEnigma/Queue/Queue.ts b/app/pages/PageEnigma/Queue/Queue.ts index e792643510..dae5ba591d 100644 --- a/app/pages/PageEnigma/Queue/Queue.ts +++ b/app/pages/PageEnigma/Queue/Queue.ts @@ -1,6 +1,7 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import { CameraAspectRatio } from "../enums"; import { QueueClip, QueueKeyframe, @@ -11,12 +12,7 @@ import { toTimelineActions } from "./toTimelineActions"; import { ClipUI } from "../datastructures/clips/clip_ui"; import { GenerationOptions } from "../models/generationOptions"; -import { ToastTypes } from "~/enums"; - -export type ToastDataType = { - type: ToastTypes; - message: string; -}; +import { ToastDataType } from "~/components"; export type UnionedActionTypes = | fromEngineActions @@ -31,6 +27,7 @@ export type UnionedDataTypes = | MediaItem | ToastDataType | GenerationOptions + | CameraAspectRatio | null; export type QueueSubscribeType = { diff --git a/app/pages/PageEnigma/Queue/fromEngineActions.ts b/app/pages/PageEnigma/Queue/fromEngineActions.ts index 8db2fe6ee2..093d913bff 100644 --- a/app/pages/PageEnigma/Queue/fromEngineActions.ts +++ b/app/pages/PageEnigma/Queue/fromEngineActions.ts @@ -1,5 +1,7 @@ export enum fromEngineActions { ADD_OBJECT = "add_object", + CAMERA_ASPECT_RATIO_CHANGED = "camera_aspect_ratio_changed", + // data = CameraAspectRatio DELETE_OBJECT = "delete_object", DESELECT_OBJECT = "deselect_object", RESET_TIMELINE = "reset_timeline", diff --git a/app/pages/PageEnigma/Queue/toEngineActions.ts b/app/pages/PageEnigma/Queue/toEngineActions.ts index c75e2ea3e6..a68b338f7a 100644 --- a/app/pages/PageEnigma/Queue/toEngineActions.ts +++ b/app/pages/PageEnigma/Queue/toEngineActions.ts @@ -27,6 +27,10 @@ export enum toEngineActions { // data=QueueClip TOGGLE_CAMERA_STATE = "toggle_camera", // data=null + + CHANGE_CAMERA_ASPECT_RATIO = "change_camera_aspect_ratio", + // data=CamperaAspectRatio + UNMUTE = "unmute", // data={version, type, group, object_uuid} if lipsync // data={version, type, group} if global_audio diff --git a/app/pages/PageEnigma/comps/AspectRatioMenu/AspectRatioMenu.tsx b/app/pages/PageEnigma/comps/AspectRatioMenu/AspectRatioMenu.tsx new file mode 100644 index 0000000000..84abe7e181 --- /dev/null +++ b/app/pages/PageEnigma/comps/AspectRatioMenu/AspectRatioMenu.tsx @@ -0,0 +1,78 @@ +import { useSignals } from "@preact/signals-react/runtime"; +import { + faRectangle, + faRectangleVertical, + faSquare, +} from "@fortawesome/pro-solid-svg-icons"; +import { CameraAspectRatio } from "~/pages/PageEnigma/enums"; +import { cameraAspectRatio } from "~/pages/PageEnigma/signals"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import { ButtonDropdown } from "~/components"; + +export const AspectRatioMenu = () => { + useSignals(); + const buttonIcon = + cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9 + ? faRectangle + : cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16 + ? faRectangleVertical + : faSquare; + const buttonText = + cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9 + ? "16:9 Horizontal" + : cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16 + ? "9:16 Vertical" + : "1:1 Squared"; + + const handleChangeAspectRatio = (newRatio: CameraAspectRatio) => { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.CHANGE_CAMERA_ASPECT_RATIO, + data: newRatio, + }); + }; + return ( +
+ + handleChangeAspectRatio(CameraAspectRatio.HORIZONTAL_16_9), + }, + { + label: "9:16", + icon: faRectangleVertical, + className: "pl-4", + description: "Vertical", + selected: + cameraAspectRatio.value === CameraAspectRatio.VERTICAL_9_16, + onClick: () => + handleChangeAspectRatio(CameraAspectRatio.VERTICAL_9_16), + }, + { + label: "1:1", + icon: faSquare, + className: "pl-4", + description: "Squared", + selected: cameraAspectRatio.value === CameraAspectRatio.SQUARE_1_1, + onClick: () => + handleChangeAspectRatio(CameraAspectRatio.SQUARE_1_1), + }, + ]} + /> +
+ ); +}; diff --git a/app/pages/PageEnigma/comps/AspectRatioMenu/index.tsx b/app/pages/PageEnigma/comps/AspectRatioMenu/index.tsx new file mode 100644 index 0000000000..cdfeca1093 --- /dev/null +++ b/app/pages/PageEnigma/comps/AspectRatioMenu/index.tsx @@ -0,0 +1 @@ +export * from "./AspectRatioMenu"; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx index b54e7b96a0..ff46ae68ad 100644 --- a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx @@ -71,7 +71,7 @@ export const ControlsTopButtons = () => { title: sceneTitleInput, }); } - }, [sceneTitleInput]); + }, [sceneTitleInput, editorEngine]); const handleButtonNewFromTemplate = () => { handleButtonLoadScene(); @@ -119,6 +119,7 @@ export const ControlsTopButtons = () => { { buttonProps={{ variant: "secondary", label: "Help", + className: "shadow-xl", }} dialogProps={{ className: "max-w-6xl w-auto", diff --git a/app/pages/PageEnigma/comps/EngineCanvases.tsx b/app/pages/PageEnigma/comps/EngineCanvases.tsx index 02cdf64ad6..362f4069c4 100644 --- a/app/pages/PageEnigma/comps/EngineCanvases.tsx +++ b/app/pages/PageEnigma/comps/EngineCanvases.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect } from "react"; +import { useCallback, useContext } from "react"; import { EngineContext } from "../contexts/EngineContext"; export const EditorCanvas = () => { @@ -23,7 +23,7 @@ export const EditorCanvas = () => { ); }; -export const CameraViewCanvas = () => { +export const CameraViewCanvas = ({ className }: { className?: string }) => { const editorEngine = useContext(EngineContext); const canvasCallbackRef = useCallback( @@ -40,5 +40,7 @@ export const CameraViewCanvas = () => { [editorEngine], ); - return ; + return ( + + ); }; diff --git a/app/pages/PageEnigma/comps/PreviewEngineCamera/PreviewEngineCamera.tsx b/app/pages/PageEnigma/comps/PreviewEngineCamera/PreviewEngineCamera.tsx new file mode 100644 index 0000000000..716303e984 --- /dev/null +++ b/app/pages/PageEnigma/comps/PreviewEngineCamera/PreviewEngineCamera.tsx @@ -0,0 +1,116 @@ +import { twMerge } from "tailwind-merge"; +import { Button, ButtonIcon, Tooltip } from "~/components"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faBlinds, + faBlindsRaised, + faCameraViewfinder, + faSpinnerThird, +} from "@fortawesome/pro-solid-svg-icons"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import { + cameraAspectRatio, + editorState, +} from "~/pages/PageEnigma/signals/engine"; +import { useSignals } from "@preact/signals-react/runtime"; +import { + editorLetterBox, + sidePanelHeight, + toggleEditorLetterBox, +} from "../../signals"; +import { CameraAspectRatio, EditorStates } from "~/pages/PageEnigma/enums"; +import { CameraViewCanvas } from "../EngineCanvases"; + +export const PreviewEngineCamera = () => { + useSignals(); + + if (editorState.value === EditorStates.PREVIEW) { + return null; + } + + const handleButtonCameraView = () => { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.TOGGLE_CAMERA_STATE, + data: null, + }); + }; + + const getScale = () => { + const height = sidePanelHeight.value; + const scaleHeight = height < 480 ? height / 480 : 1; + return scaleHeight; + }; + + return ( +
+
+
+
+ +

Camera View

+
+ +
+ {editorState.value === EditorStates.CAMERA_VIEW && ( + + toggleEditorLetterBox()} + /> + + )} + + +
+
+
+
+ +
+
+ +
+
+
+
+ ); +}; diff --git a/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx b/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx index 39ceed8bd9..e39a79bd8f 100644 --- a/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx +++ b/app/pages/PageEnigma/comps/PreviewEngineCamera/index.tsx @@ -1,72 +1 @@ -import { useEffect, useState } from "react"; -import { Button, LoadingDotsTyping } from "~/components"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCameraViewfinder } from "@fortawesome/pro-solid-svg-icons"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { editorState } from "~/pages/PageEnigma/signals/engine"; -import { useSignals } from "@preact/signals-react/runtime"; -import { sidePanelHeight } from "../../signals"; -import { EditorStates } from "~/pages/PageEnigma/enums"; -import { CameraViewCanvas } from "../EngineCanvases"; - -export const PreviewEngineCamera = () => { - useSignals(); - const [showLoader, setShowLoader] = useState(true); - - useEffect(() => { - setTimeout(() => setShowLoader(false), 1000); - }, []); - - if (editorState.value === EditorStates.PREVIEW) { - return null; - } - - const handleButtonCameraView = () => { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.TOGGLE_CAMERA_STATE, - data: null, - }); - }; - - const getScale = () => { - const height = sidePanelHeight.value; - const scaleHeight = height < 480 ? height / 480 : 1; - return scaleHeight; - }; - - return ( -
-
-
-
- -

Camera View

-
- - -
-
- -
- -
-
-
-
- ); -}; +export * from "./PreviewEngineCamera"; diff --git a/app/pages/PageEnigma/comps/SceneContainer/Letterbox.tsx b/app/pages/PageEnigma/comps/SceneContainer/Letterbox.tsx new file mode 100644 index 0000000000..612f01926a --- /dev/null +++ b/app/pages/PageEnigma/comps/SceneContainer/Letterbox.tsx @@ -0,0 +1,88 @@ +import { useSignals } from "@preact/signals-react/runtime"; +import { twMerge } from "tailwind-merge"; + +import { EditorStates } from "~/pages/PageEnigma/enums"; +import { editorState, cameraAspectRatio } from "~/pages/PageEnigma/signals"; + +import { + calcMatteWidth, + calcMatteHeight, + getMatteOrientation, + MatteOrientation, +} from "./helpers"; + +export const Letterbox = ({ + isShowing, + width, + height, +}: { + isShowing: boolean; + width: number; + height: number; +}) => { + useSignals(); + + if (editorState.value !== EditorStates.CAMERA_VIEW || !isShowing) { + return; + } + + const matteOri = getMatteOrientation({ + camAspect: cameraAspectRatio.value, + width, + height, + }); + + const matteWidth = calcMatteWidth({ + matteOri, + camAspect: cameraAspectRatio.value, + width, + height, + }); + + const matteHeight = calcMatteHeight({ + matteOri, + camAspect: cameraAspectRatio.value, + width, + height, + }); + console.log(matteOri, "matteOri"); + console.log(matteWidth, "matteWidth"); + console.log(matteHeight, "matteHeight"); + return ( +
+ + +
+ ); +}; + +const Matte = ({ + matteOri, + width, + height, +}: { + matteOri: MatteOrientation; + width?: number; + height?: number; +}) => { + if (matteOri === MatteOrientation.TOP_BOTTOM) { + return ( +
+ ); + } + return ( +
+ ); +}; diff --git a/app/pages/PageEnigma/comps/SceneContainer.tsx b/app/pages/PageEnigma/comps/SceneContainer/SceneContainer.tsx similarity index 61% rename from app/pages/PageEnigma/comps/SceneContainer.tsx rename to app/pages/PageEnigma/comps/SceneContainer/SceneContainer.tsx index 2247795c65..caa5e9e841 100644 --- a/app/pages/PageEnigma/comps/SceneContainer.tsx +++ b/app/pages/PageEnigma/comps/SceneContainer/SceneContainer.tsx @@ -1,17 +1,26 @@ import { useCallback, useContext } from "react"; import { useSignals } from "@preact/signals-react/runtime"; -import { EngineContext } from "../contexts/EngineContext"; + +import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; import { pageHeight, pageWidth } from "~/signals"; import { timelineHeight, sidePanelWidth, sidePanelVisible, + editorLetterBox, } from "~/pages/PageEnigma/signals"; +import { Letterbox } from "./Letterbox"; + export const SceneContainer = ({ children }: { children: React.ReactNode }) => { useSignals(); const editorEngine = useContext(EngineContext); + const containerWidth = + pageWidth.value - (sidePanelVisible.value ? sidePanelWidth.value : 0) - 84; + + const containerHeight = pageHeight.value - timelineHeight.value - 68; + const callbackRef = useCallback( (node: HTMLDivElement) => { if (node && editorEngine && editorEngine.setSceneContainer) { @@ -27,14 +36,16 @@ export const SceneContainer = ({ children }: { children: React.ReactNode }) => { id="video-scene-container" className="relative" style={{ - width: - pageWidth.value - - (sidePanelVisible.value ? sidePanelWidth.value : 0) - - 84, - height: pageHeight.value - timelineHeight.value - 68, + width: containerWidth, + height: containerHeight, }} > {children} +
); }; diff --git a/app/pages/PageEnigma/comps/SceneContainer/helpers.ts b/app/pages/PageEnigma/comps/SceneContainer/helpers.ts new file mode 100644 index 0000000000..c4badb023e --- /dev/null +++ b/app/pages/PageEnigma/comps/SceneContainer/helpers.ts @@ -0,0 +1,95 @@ +import { CameraAspectRatio } from "../../enums"; + +export enum MatteOrientation { + TOP_BOTTOM, + LEFT_RIGHT, +} + +export const getMatteOrientation = ({ + camAspect, + width, + height, +}: { + camAspect: CameraAspectRatio; + width: number; + height: number; +}): MatteOrientation => { + switch (camAspect) { + case CameraAspectRatio.SQUARE_1_1: { + return width > height + ? MatteOrientation.LEFT_RIGHT + : MatteOrientation.TOP_BOTTOM; + } + case CameraAspectRatio.VERTICAL_9_16: { + return (height / 16) * 9 - width >= 0 + ? MatteOrientation.TOP_BOTTOM + : MatteOrientation.LEFT_RIGHT; + } + case CameraAspectRatio.HORIZONTAL_16_9: + default: { + return (width / 16) * 9 - height >= 0 + ? MatteOrientation.LEFT_RIGHT + : MatteOrientation.TOP_BOTTOM; + } + } +}; + +export const calcMatteWidth = ({ + matteOri, + camAspect, + width, + height, +}: { + matteOri: MatteOrientation; + camAspect: CameraAspectRatio; + width: number; + height: number; +}): number | undefined => { + if (matteOri === MatteOrientation.TOP_BOTTOM) { + return undefined; + } + switch (camAspect) { + case CameraAspectRatio.SQUARE_1_1: { + return (width - height) / 2; + } + case CameraAspectRatio.HORIZONTAL_16_9: { + return (width - (height / 9) * 16) / 2; + } + case CameraAspectRatio.VERTICAL_9_16: { + return (width - (height / 16) * 9) / 2; + } + default: { + return undefined; + } + } +}; + +export const calcMatteHeight = ({ + matteOri, + camAspect, + width, + height, +}: { + matteOri: MatteOrientation; + camAspect: CameraAspectRatio; + width: number; + height: number; +}): number | undefined => { + if (matteOri === MatteOrientation.LEFT_RIGHT) { + return undefined; + } + switch (camAspect) { + case CameraAspectRatio.SQUARE_1_1: { + return (height - width) / 2; + } + case CameraAspectRatio.HORIZONTAL_16_9: { + return (height - (width / 16) * 9) / 2; + } + case CameraAspectRatio.VERTICAL_9_16: { + return (height - (width / 9) * 16) / 2; + } + default: { + return undefined; + } + } +}; diff --git a/app/pages/PageEnigma/comps/SceneContainer/index.tsx b/app/pages/PageEnigma/comps/SceneContainer/index.tsx new file mode 100644 index 0000000000..7084d5441c --- /dev/null +++ b/app/pages/PageEnigma/comps/SceneContainer/index.tsx @@ -0,0 +1 @@ +export * from "./SceneContainer"; diff --git a/app/pages/PageEnigma/enums/EditorState.ts b/app/pages/PageEnigma/enums/EditorState.ts index 51b4d0d71c..70375c046b 100644 --- a/app/pages/PageEnigma/enums/EditorState.ts +++ b/app/pages/PageEnigma/enums/EditorState.ts @@ -3,3 +3,9 @@ export enum EditorStates { CAMERA_VIEW, PREVIEW, } + +export enum CameraAspectRatio { + HORIZONTAL_16_9 = "horizontal_16_9", + VERTICAL_9_16 = "vertical_9_16", + SQUARE_1_1 = "SQUARE_1_1", +} diff --git a/app/pages/PageEnigma/hooks/useQueueHandler.ts b/app/pages/PageEnigma/hooks/useQueueHandler.ts index 2dd9ff4a9d..850e3ccdc6 100644 --- a/app/pages/PageEnigma/hooks/useQueueHandler.ts +++ b/app/pages/PageEnigma/hooks/useQueueHandler.ts @@ -14,21 +14,22 @@ import { selectedObject, addKeyframe, } from "~/pages/PageEnigma/signals"; -import Queue, { - QueueSubscribeType, - ToastDataType, -} from "~/pages/PageEnigma/Queue/Queue"; +import Queue, { QueueSubscribeType } from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; + +import { ToastDataType } from "~/components"; + import { MediaItem, QueueKeyframe, UpdateTime, } from "~/pages/PageEnigma/models"; -import { ClipGroup } from "~/pages/PageEnigma/enums"; +import { CameraAspectRatio, ClipGroup } from "~/pages/PageEnigma/enums"; import { addToast } from "~/signals"; import { ToastTypes } from "~/enums"; import { toTimelineActions } from "~/pages/PageEnigma/Queue/toTimelineActions"; import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui"; +import { cameraAspectRatio } from "../signals/engine"; const LOADING_FUNCTIONS: Record void> = { [ClipGroup.CHARACTER]: loadCharacterData, @@ -82,11 +83,15 @@ export function useQueueHandler() { addToast(ToastTypes.ERROR, message); break; } + case fromEngineActions.CAMERA_ASPECT_RATIO_CHANGED: { + cameraAspectRatio.value = data as CameraAspectRatio; + break; + } default: throw new Error(`Unknown action ${action}`); } }, - [deleteObjectOrCharacter], + [], ); const handleToTimelineActions = useCallback( diff --git a/app/pages/PageEnigma/signals/editorLoader.ts b/app/pages/PageEnigma/signals/editorOverlays.ts similarity index 71% rename from app/pages/PageEnigma/signals/editorLoader.ts rename to app/pages/PageEnigma/signals/editorOverlays.ts index 8524ea2a33..be67dd8a22 100644 --- a/app/pages/PageEnigma/signals/editorLoader.ts +++ b/app/pages/PageEnigma/signals/editorOverlays.ts @@ -21,3 +21,10 @@ export const hideEditorLoader = () => { // it is important to keep the message so the loader fades out nicely }; }; + +export const editorLetterBox = signal(true); + +export const toggleEditorLetterBox = (newState?: boolean) => { + editorLetterBox.value = + newState !== undefined ? newState : !editorLetterBox.value; +}; diff --git a/app/pages/PageEnigma/signals/engine.ts b/app/pages/PageEnigma/signals/engine.ts index e0bc593c4e..e0609880ad 100644 --- a/app/pages/PageEnigma/signals/engine.ts +++ b/app/pages/PageEnigma/signals/engine.ts @@ -1,6 +1,9 @@ import { signal } from "@preact/signals-core"; -import { EditorStates } from "~/pages/PageEnigma/enums"; +import { CameraAspectRatio, EditorStates } from "~/pages/PageEnigma/enums"; export const editorState = signal(EditorStates.EDIT); +export const cameraAspectRatio = signal( + CameraAspectRatio.HORIZONTAL_16_9, +); export const previewSrc = signal(""); diff --git a/app/pages/PageEnigma/signals/index.ts b/app/pages/PageEnigma/signals/index.ts index 87319afd87..183e57aef0 100644 --- a/app/pages/PageEnigma/signals/index.ts +++ b/app/pages/PageEnigma/signals/index.ts @@ -1,7 +1,8 @@ export * from "./audioGroup"; export * from "./cameraGroup"; export * from "./characterGroups"; -export * from "./editorLoader"; +export * from "./editorOverlays"; +export * from "./engine"; export * from "./dragAndDrop"; export * from "./hotkeys"; export * from "./media";