mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
Merge pull request #44 from storytold/feature/scene-object_control-panel
Feature/scene object control panel
This commit is contained in:
13 files changed
+307
-31
No files matched your search
@@ -33,14 +33,14 @@ export const Button = ({
|
||||
}
|
||||
|
||||
const className = twMerge(
|
||||
"text-sm font-semibold rounded-md px-3 py-2 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-all duration-150",
|
||||
"text-sm font-semibold rounded-md px-3 py-2 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-all duration-150 flex gap-2 items-center",
|
||||
getVariantClassNames(variant),
|
||||
propsClassName,
|
||||
);
|
||||
|
||||
return (
|
||||
<button className={className} disabled={disabled} {...rest}>
|
||||
{icon && <FontAwesomeIcon className="mr-2" icon={icon} />}
|
||||
{icon && <FontAwesomeIcon icon={icon} />}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -13,13 +13,7 @@ export function Input({ label, className, ...rest }: InputProps) {
|
||||
)}
|
||||
|
||||
<input
|
||||
className="h-10 rounded-md bg-brand-secondary p-3 text-white"
|
||||
style={{
|
||||
outline: "2px solid transparent",
|
||||
transition: "outline-color 0.15s ease-in-out",
|
||||
}}
|
||||
onFocus={(e) => (e.currentTarget.style.outlineColor = "#e66462")}
|
||||
onBlur={(e) => (e.currentTarget.style.outlineColor = "transparent")}
|
||||
className="h-10 rounded-md bg-brand-secondary p-3 text-white transition-all duration-150 ease-in-out outline-none outline-offset-0 focus:outline-brand-primary"
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useRef } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
|
||||
interface InputVectorProps {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
onChange : (v:{
|
||||
x:number;
|
||||
y:number;
|
||||
z:number;
|
||||
})=>void;
|
||||
}
|
||||
|
||||
export const InputVector = ({
|
||||
x,y,z,
|
||||
onChange
|
||||
}:InputVectorProps)=>{
|
||||
const xRef = useRef<HTMLInputElement>(null);
|
||||
const yRef = useRef<HTMLInputElement>(null);
|
||||
const zRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const inputCommonClasses ="relative w-14 h-8 rounded-r-md bg-brand-secondary p-3 text-base text-white transition-all duration-150 ease-in-out outline-none -outline-offset-2 ";
|
||||
|
||||
const wrapperCommonClasses = "flex items-center before:inline-block before:w-10 before:h-10 before:bg-brand-primary before:text-white before:rounded-l-md before:h-8 before:w-4 before:text-center before:align-middle before:leading-8 text-xs";
|
||||
|
||||
function handleOnChange(){
|
||||
const newVector = {
|
||||
x: Number(xRef.current?.value),
|
||||
y: Number(yRef.current?.value),
|
||||
z: Number(zRef.current?.value),
|
||||
}
|
||||
onChange(newVector);
|
||||
}
|
||||
return(
|
||||
<div className="w-full flex justify-between gap-2">
|
||||
<span className={twMerge(wrapperCommonClasses, "before:bg-axis-x before:content-['X']")}>
|
||||
<input
|
||||
className={twMerge(inputCommonClasses, "focus:outline-axis-x")}
|
||||
onChange={handleOnChange}
|
||||
ref={xRef}
|
||||
value={x}
|
||||
|
||||
/>
|
||||
</span>
|
||||
<span className={twMerge(wrapperCommonClasses, "before:bg-axis-y before:content-['Y']")}>
|
||||
<input
|
||||
className={twMerge(inputCommonClasses, "focus:outline-axis-y")}
|
||||
onChange={handleOnChange}
|
||||
ref={yRef}
|
||||
value={y}
|
||||
/>
|
||||
</span>
|
||||
<span className={twMerge(wrapperCommonClasses, "before:bg-axis-z before:content-['Z']")}>
|
||||
<input
|
||||
className={twMerge(inputCommonClasses, "focus:outline-axis-z")}
|
||||
onChange={handleOnChange}
|
||||
ref={zRef}
|
||||
value={z}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { ButtonIcon } from "./ButtonIcon";
|
||||
import { ButtonLink } from "./ButtonLink";
|
||||
import { ButtonIconSelect } from "./ButtonIconSelect";
|
||||
import { Input } from "./Input";
|
||||
import { InputVector } from "./InputVector";
|
||||
import { H1, H2, H3, H4, H5, H6, P, Label} from "./Typography";
|
||||
import { ItemPicker } from "./ItemPicker";
|
||||
import { Textarea } from "./Textarea";
|
||||
@@ -18,6 +19,7 @@ export {
|
||||
ItemPicker,
|
||||
Textarea,
|
||||
Input,
|
||||
InputVector,
|
||||
LoadingBar,
|
||||
LoadingDots,
|
||||
LoadingDotsBricks,
|
||||
|
||||
@@ -5,12 +5,16 @@ import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, LoadingBar, LoadingDots } from "~/components";
|
||||
import { TopBarHelmet } from "~/modules/TopBarHelmet/TopBarHelmet";
|
||||
import { SidePanel } from "~/modules/SidePanel";
|
||||
|
||||
import { Controls3D } from "./comps/Controls3D";
|
||||
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
|
||||
import { ControlsVideo } from "./comps/ControlsVideo";
|
||||
import { ControlPanelSceneObject } from "./comps/ControlPanelSceneObject";
|
||||
import { PreviewEngineCamera } from "./comps/PreviewEngineCamera";
|
||||
import { Timeline } from "./comps/Timeline";
|
||||
import { ViewSideBySide } from "./comps/ViewSideBySide";
|
||||
import { SidePanelTabs } from "./comps/SidePanelTabs";
|
||||
import { Timeline } from "./comps/Timeline";
|
||||
|
||||
|
||||
import { EngineProvider } from "./contexts/EngineProvider";
|
||||
import { AppUIProvider } from "./contexts/AppUiContext";
|
||||
@@ -19,7 +23,6 @@ import {
|
||||
appUiInitialStateValues,
|
||||
APPUI_VIEW_MODES,
|
||||
} from "./reducers";
|
||||
import { ViewSideBySide } from "./comps/ViewSideBySide";
|
||||
import {
|
||||
canDrop,
|
||||
clipLength,
|
||||
@@ -52,7 +55,7 @@ export const PageEnigmaComponent = () => {
|
||||
<div style={{ height: "calc(100vh - 68px)" }}>
|
||||
{/* Engine section/side panel */}
|
||||
<div
|
||||
id="CanvasUiWrapper"
|
||||
id="engine-n-panels-wrapper"
|
||||
className="flex"
|
||||
style={{ height: `calc(100% - ${lowerHeight}px)` }}
|
||||
>
|
||||
@@ -78,6 +81,7 @@ export const PageEnigmaComponent = () => {
|
||||
<div className="absolute bottom-0 left-0 w-full">
|
||||
<PreviewEngineCamera />
|
||||
<ControlsVideo />
|
||||
<ControlPanelSceneObject/>
|
||||
</div>
|
||||
</div>
|
||||
{appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && (
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { useContext } from 'react';
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { faArrowRightArrowLeft, faCube, faTrash} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { AppUiContext } from '../../contexts/AppUiContext';
|
||||
import { Button, H4, H6, InputVector } from "~/components";
|
||||
|
||||
import { XYZ } from '../../datastructures/common';
|
||||
import { ACTION_TYPES } from '../../reducers/appUiReducer/types';
|
||||
|
||||
|
||||
|
||||
export const ControlPanelSceneObject = () => {
|
||||
const [appUiState, dispatchAppUiState ] = useContext(AppUiContext);
|
||||
|
||||
const position = appUiState?.currentSceneObject.objectVectors.position;
|
||||
const rotation = appUiState?.currentSceneObject.objectVectors.rotation;
|
||||
const scalar = appUiState?.currentSceneObject.objectVectors.scalar;
|
||||
|
||||
const handlePositionChange = (xyz:XYZ)=>{
|
||||
if(appUiState)
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject:{
|
||||
objectVectors:{
|
||||
position: {...xyz},
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
scalar:appUiState.currentSceneObject.objectVectors.scalar,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const handleRotationChange = (xyz:XYZ)=>{
|
||||
if(appUiState)
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject:{
|
||||
objectVectors:{
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: {...xyz},
|
||||
scalar:appUiState.currentSceneObject.objectVectors.scalar,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
const handleScalarChange = (xyz:XYZ)=>{
|
||||
if(appUiState)
|
||||
dispatchAppUiState({
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject:{
|
||||
objectVectors:{
|
||||
position: appUiState.currentSceneObject.objectVectors.position,
|
||||
rotation: appUiState.currentSceneObject.objectVectors.rotation,
|
||||
scalar:{...xyz},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return(
|
||||
<Transition
|
||||
show={appUiState?.currentSceneObject.isShowing}
|
||||
className="absolute bottom-0 right-0 w-fit h-fit m-4 p-2 bg-ui-panel border border-ui-panel-border text-white rounded-lg flex flex-col gap-2"
|
||||
enter="transition-opacity duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-1000"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="flex justify-between gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faCube}/>
|
||||
<p>Object Name</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faArrowRightArrowLeft} size="xs" />
|
||||
<H6>Swap Object</H6>
|
||||
</div>
|
||||
</div>
|
||||
<span className='h-1'/>
|
||||
<H4>Position</H4>
|
||||
<InputVector
|
||||
x={position?.x || 0}
|
||||
y={position?.y || 0}
|
||||
z={position?.z || 0}
|
||||
onChange={handlePositionChange}
|
||||
/>
|
||||
<H4>Rotation</H4>
|
||||
<InputVector
|
||||
x={rotation?.x || 0}
|
||||
y={rotation?.y || 0}
|
||||
z={rotation?.z || 0}
|
||||
onChange={handleRotationChange}
|
||||
/>
|
||||
<H4>Scale</H4>
|
||||
<InputVector
|
||||
x={scalar?.x || 0}
|
||||
y={scalar?.y || 0}
|
||||
z={scalar?.z || 0}
|
||||
onChange={handleScalarChange}
|
||||
/>
|
||||
<span className='h-2'/>
|
||||
<div className="flex justify-between gap-2">
|
||||
<Button variant="secondary">Add Keyframe (K)</Button>
|
||||
<Button variant="secondary" icon={faTrash} />
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { ControlPanelSceneObject } from './ControlPanelSceneObject';
|
||||
@@ -21,15 +21,6 @@ export const ControlsTopButtons = () => {
|
||||
editorEngine?.loadScene(sceneToken);
|
||||
};
|
||||
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
editorEngine?.switchCameraView();
|
||||
};
|
||||
|
||||
const handleButtonPlayBack = () => {
|
||||
editorEngine?.startPlayback();
|
||||
};
|
||||
|
||||
const handleButtonTest = () => {
|
||||
if(appUiState?.showEditorLoadingBar.isShowing){
|
||||
//TO GO TO A HUNDRED AND DISAPPER
|
||||
@@ -71,10 +62,33 @@ export const ControlsTopButtons = () => {
|
||||
}
|
||||
};
|
||||
|
||||
//const handleButtonRender = () => {
|
||||
// editorEngine?.take_timeline_cam_clip();
|
||||
//};
|
||||
const handleObjectPanelTest = () => {
|
||||
if (appUiState?.currentSceneObject.isShowing) {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT
|
||||
});
|
||||
}else{
|
||||
dispatchAppUiState({
|
||||
type:APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject:{
|
||||
objectVectors: {
|
||||
position: {x:1,y:2,z:3},
|
||||
rotation: {x:4,y:5,z:6},
|
||||
scalar: {x:7,y:8,z:9},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
editorEngine?.switchCameraView();
|
||||
};
|
||||
const handleButtonPlayBack = () => {
|
||||
editorEngine?.startPlayback();
|
||||
};
|
||||
const handleButtonRender = () => {
|
||||
editorEngine?.generateVideo();
|
||||
};
|
||||
@@ -83,12 +97,7 @@ export const ControlsTopButtons = () => {
|
||||
editorEngine?.take_timeline_cam_clip();
|
||||
};
|
||||
|
||||
// const handleButtonCameraView = () => {
|
||||
// editorEngine?.change_camera_view();
|
||||
// };
|
||||
|
||||
const handleButtonLoad = () => {};
|
||||
// const handleButtonRender = () => {};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pl-3 pt-3">
|
||||
@@ -147,6 +156,12 @@ export const ControlsTopButtons = () => {
|
||||
<Button variant="secondary" onClick={handleButtonCameraView}>
|
||||
Toggle Camera View
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleObjectPanelTest}
|
||||
className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary"
|
||||
>
|
||||
Show Object Panel
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export type XYZ = {
|
||||
x :number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export type Simple3DVector = {
|
||||
position: XYZ;
|
||||
rotation: XYZ;
|
||||
scalar: XYZ;
|
||||
}
|
||||
@@ -2,6 +2,33 @@ import { State, Action, ACTION_TYPES} from './types';
|
||||
|
||||
export function reducer(state: State, action: Action): State {
|
||||
switch(action.type){
|
||||
case ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT:{
|
||||
return{
|
||||
...state,
|
||||
currentSceneObject: {
|
||||
isShowing: true,
|
||||
objectVectors: {...action.payload.currentSceneObject.objectVectors},
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT:{
|
||||
return{
|
||||
...state,
|
||||
currentSceneObject: {
|
||||
isShowing: state.currentSceneObject.isShowing,
|
||||
objectVectors: {...action.payload.currentSceneObject.objectVectors},
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT:{
|
||||
return{
|
||||
...state,
|
||||
currentSceneObject: {
|
||||
isShowing: false,
|
||||
objectVectors: state.currentSceneObject.objectVectors,
|
||||
},
|
||||
}
|
||||
}
|
||||
case ACTION_TYPES.SHOW_EDITOR_LOADER:{
|
||||
return{
|
||||
...state,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Simple3DVector } from "../../datastructures/common";
|
||||
export enum VIEW_MODES {
|
||||
EDITOR = 'editor',
|
||||
SIDE_BY_SIDE = 'side-by-side'
|
||||
@@ -19,6 +20,10 @@ export type State = {
|
||||
progress?: number;
|
||||
useFakeTimer?: number;
|
||||
}
|
||||
currentSceneObject:{
|
||||
isShowing: boolean;
|
||||
objectVectors: Simple3DVector;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,9 +35,29 @@ export enum ACTION_TYPES {
|
||||
SHOW_EDITOR_LOADINGBAR = "show_editor_loadingbar",
|
||||
UPDATE_EDITOR_LOADINGBAR = "update_editor_loadingbar",
|
||||
HIDE_EDITOR_LOADINGBAR = "hide_editor_loadingbar",
|
||||
SHOW_CONTROLPANELS_SCENEOBJECT = "show_controlpanels_sceneobject",
|
||||
UPDATE_CONTROLPANELS_SCENEOBJECT = "update_controlpanels_sceneobject",
|
||||
HIDE_CONTROLPANELS_SCENEOBJECT = "hide_controlpanels_sceneobject",
|
||||
};
|
||||
|
||||
export type Action =
|
||||
export type Action =
|
||||
| {type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT}
|
||||
| {
|
||||
type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject: {
|
||||
objectVectors: Simple3DVector;
|
||||
};
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
payload:{
|
||||
currentSceneObject: {
|
||||
objectVectors: Simple3DVector;
|
||||
};
|
||||
}
|
||||
}
|
||||
| {type: ACTION_TYPES.HIDE_EDITOR_LOADER,}
|
||||
| {
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER,
|
||||
|
||||
@@ -11,5 +11,13 @@ export const initialStateValues : State = {
|
||||
isShowing:true,
|
||||
progress:5,
|
||||
message: 'Loading Editor Engine 🦊'
|
||||
},
|
||||
currentSceneObject:{
|
||||
isShowing:false,
|
||||
objectVectors:{
|
||||
position:{x:0,y:0,z:0},
|
||||
rotation:{x:0,y:0,z:0},
|
||||
scalar:{x:0,y:0,z:0},
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -126,9 +126,16 @@ export default {
|
||||
"ui-controls-button": "#676781",
|
||||
"ui-divider": "#515168",
|
||||
|
||||
"axis-x" :"#D33242",
|
||||
"axis-y" :"#308752",
|
||||
"axis-z" :"#2E70FF",
|
||||
red: "#D33242",
|
||||
green:"#308752",
|
||||
blue: "#2E70FF",
|
||||
|
||||
white: colors.white,
|
||||
gray: colors.gray,
|
||||
black: colors.black,
|
||||
white: colors.white,
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
Reference in new issue
Block a user