Skip to content

Commit

Permalink
Add 3D Canvas demo
Browse files Browse the repository at this point in the history
  • Loading branch information
joserodolfofreitas committed May 22, 2024
1 parent 4a77854 commit e75aa7b
Show file tree
Hide file tree
Showing 14 changed files with 1,438 additions and 0 deletions.
80 changes: 80 additions & 0 deletions docs/data/tree-view/rich-tree-view/customization/ThreeDCanvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import CustomTreeItem from './three-d/CustomTreeItem';
import CustomTreeItemContextMenu from './three-d/ContextMenu';
import Scene from './three-d/Scene';
import sceneObjects from './three-d/SceneObjects';
import ThemeProvider from '@mui/material/styles/ThemeProvider';
import { createTheme } from '@mui/material/styles';

const darkTheme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#5ecaff',
},
background: {
default: '#f5f5f5', // Replace with your desired background color
},
},
});

export default function Demo() {
const [objectsToRender, setObjectsToRender] = React.useState(sceneObjects);
const [expandedNodes, setExpandedNodes] = React.useState([
'lights',
'chassi',
'wheels',
'car',
]);

const handleExpandedNodesChange = (event, nodeIds) => {
setExpandedNodes(nodeIds);
};

const toggleVisibility = (nodeId, items = objectsToRender) => {
items.forEach((item) => {
if (item.id === nodeId) {
item.visibility = !item.visibility;
}
if (item.children && item.children.length > 0) {
// do it recursively
toggleVisibility(nodeId, item.children);
}
});
setObjectsToRender([...items]);
};
return (
<ThemeProvider theme={darkTheme}>
<div
style={{
minHeight: 200,
flexGrow: 1,
display: 'flex',
flexDirection: 'row',
background: 'black',
alignContent: 'center',
maxWidth: '800px',
borderRadius: '30px',
overflow: 'hidden',
margin: '10px auto',
}}
>
<RichTreeView
items={sceneObjects}
expandedItems={expandedNodes}
onExpandedItemsChange={handleExpandedNodesChange}
sx={{ minWidth: '50%', paddingTop: '60px', paddingLeft: '20px' }}
slots={{ item: CustomTreeItem }}
slotProps={{
item: {
toggleVisibility: toggleVisibility,
sceneObjects: objectsToRender, //temporarily solution
},
}}
/>
<Scene objects={objectsToRender} />
</div>
</ThemeProvider>
);
}
83 changes: 83 additions & 0 deletions docs/data/tree-view/rich-tree-view/customization/ThreeDCanvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import CustomTreeItem from './three-d/CustomTreeItem';
import CustomTreeItemContextMenu from './three-d/ContextMenu';
import Scene from './three-d/Scene';
import sceneObjects from './three-d/SceneObjects';
import ThemeProvider from '@mui/material/styles/ThemeProvider';
import { createTheme } from '@mui/material/styles';

const darkTheme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#5ecaff',
},
background: {
default: '#f5f5f5', // Replace with your desired background color
},
},
});

export default function Demo() {
const [objectsToRender, setObjectsToRender] = React.useState(sceneObjects);
const [expandedNodes, setExpandedNodes] = React.useState<string[]>([
'lights',
'chassi',
'wheels',
'car',
]);

const handleExpandedNodesChange = (
event: React.SyntheticEvent,
nodeIds: string[]
) => {
setExpandedNodes(nodeIds);
};

const toggleVisibility = (nodeId, items = objectsToRender) => {
items.forEach((item) => {
if (item.id === nodeId) {
item.visibility = !item.visibility;
}
if (item.children && item.children.length > 0) {
// do it recursively
toggleVisibility(nodeId, item.children);
}
});
setObjectsToRender([...items]);
};
return (
<ThemeProvider theme={darkTheme}>
<div
style={{
minHeight: 200,
flexGrow: 1,
display: 'flex',
flexDirection: 'row',
background: 'black',
alignContent: 'center',
maxWidth: '800px',
borderRadius: '30px',
overflow: 'hidden',
margin: '10px auto',
}}
>
<RichTreeView
items={sceneObjects}
expandedItems={expandedNodes}
onExpandedItemsChange={handleExpandedNodesChange}
sx={{ minWidth: '50%', paddingTop: '60px', paddingLeft: '20px' }}
slots={{ item: CustomTreeItem }}
slotProps={{
item: {
toggleVisibility: toggleVisibility,
sceneObjects: objectsToRender, //temporarily solution
},
}}
/>
<Scene objects={objectsToRender} />
</div>
</ThemeProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,10 @@ You can learn more about this new component in the [Overview page](/x/react-tree
The demo below shows many of the previous customization examples brought together to make the Tree View component look completely different than its default design.

{{"demo": "FileExplorer.js", "defaultCodeOpen": false}}


### 3D Canvas

This example is built using the new `useTreeItem`.

{{"demo": "ThreeDCanvas.js", "defaultCodeOpen": false}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';

export default function CustomTreeItemContextMenu(props) {
const { positionSeed, onClose, onClick, menuItems } = props;

const open = Boolean(positionSeed);

const { x, y } = positionSeed ? positionSeed : { x: null, y: null };
return (
<Menu
open={open}
onClose={onClose}
anchorReference="anchorPosition"
anchorPosition={positionSeed !== null ? { top: y, left: x } : undefined}
>
{menuItems.map((item, index) => (
<MenuItem
key={index}
onClick={() => {
onClick(item);
}}
>
{item}
</MenuItem>
))}
</Menu>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import React from 'react';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';

interface ContextMenuProps {
positionSeed: { x: number; y: number } | null;
onClose: () => void;
onClick: (menuItem: string) => void;
menuItems: string[]; //you may want to have another type for
}

export default function CustomTreeItemContextMenu(props: ContextMenuProps) {
const { positionSeed, onClose, onClick, menuItems } = props;

const open = Boolean(positionSeed);

const { x, y } = positionSeed ? positionSeed : { x: null, y: null };
return (
<Menu
open={open}
onClose={onClose}
anchorReference="anchorPosition"
anchorPosition={positionSeed !== null ? { top: y, left: x } : undefined}
>
{menuItems.map((item, index) => (
<MenuItem
key={index}
onClick={() => {
onClick(item);
}}
>
{item}
</MenuItem>
))}
</Menu>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React from 'react';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import ViewInArOutlinedIcon from '@mui/icons-material/ViewInArOutlined';
import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined';
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
import { unstable_useTreeItem2 as useTreeItem2 } from '@mui/x-tree-view/useTreeItem2';
import {
TreeItem2Content,
TreeItem2IconContainer,
TreeItem2Label,
TreeItem2Root,
TreeItem2GroupTransition,
} from '@mui/x-tree-view/TreeItem2';
import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon';
import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider';
import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';

import { findItemById } from './SceneObjects'; //temporarily solution
import CustomTreeItemContextMenu from './ContextMenu';

const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({
padding: theme.spacing(0.5, 1),
}));

const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
const { id, itemId, label, children, sceneObjects, toggleVisibility, ...other } =
props;

const {
getRootProps,
getContentProps,
getIconContainerProps,
getLabelProps,
getGroupTransitionProps,
status,
} = useTreeItem2({ id, itemId, children, label, rootRef: ref });

const originalItem = findItemById(sceneObjects, itemId);
const { visibility, type } = originalItem;

const [mousePosition, setMousePosition] = React.useState(null);

const handleContextMenu = (event) => {
event.preventDefault();
setMousePosition({
x: event.clientX - 2,
y: event.clientY - 4,
});
};

const handleContextMenuClose = () => {
setMousePosition(null); // Closes the context menu
};

const handleContextMenuItemClick = (action) => {
console.log(action);
handleContextMenuClose();
};

const handleEyeClick = () => {
toggleVisibility(itemId);
};

const { interactions } = useTreeItem2Utils({
itemId: props.itemId,
children: props.children,
});

const handleContentClick = (event) => {
event.defaultMuiPrevented = true;
interactions.handleSelection(event);
};

const handleIconContainerClick = (event) => {
interactions.handleExpansion(event);
};

const ItemIcon = () => {
switch (type) {
case 'mesh':
return <ViewInArOutlinedIcon style={{ color: 'darkolivegreen' }} />;
case 'light':
return <LightbulbOutlinedIcon style={{ color: 'yellow' }} />;
case 'collection':
return <FolderOutlinedIcon style={{ color: 'gray' }} />;
default:
return null;
}
};

return (
<TreeItem2Provider itemId={itemId}>
<TreeItem2Root {...getRootProps(other)}>
<CustomTreeItemContent
{...getContentProps()}
onContextMenu={handleContextMenu}
>
<TreeItem2IconContainer
{...getIconContainerProps()}
onClick={handleIconContainerClick}
>
<TreeItem2Icon status={status} />
</TreeItem2IconContainer>
<Box
sx={{ flexGrow: 1, display: 'flex', gap: 1 }}
onClick={handleContentClick}
>
{visibility ? (
<VisibilityIcon
sx={(theme) => ({
color: theme.palette.primary.main,
width: 24,
height: 24,
})}
onClick={handleEyeClick}
/>
) : (
<VisibilityOffIcon
sx={{ width: 24, height: 24, color: '#555' }}
onClick={handleEyeClick}
/>
)}

<ItemIcon />
<TreeItem2Label
{...getLabelProps()}
sx={(theme) => ({
color: visibility ? theme.palette.text.primary : '#888',
})}
/>
</Box>
</CustomTreeItemContent>
{children && <TreeItem2GroupTransition {...getGroupTransitionProps()} />}
</TreeItem2Root>
<CustomTreeItemContextMenu
positionSeed={mousePosition}
onClose={handleContextMenuClose}
onClick={handleContextMenuItemClick}
menuItems={['Action 1', 'Action 2', 'Action 3']}
/>
</TreeItem2Provider>
);
});

export default CustomTreeItem;
Loading

0 comments on commit e75aa7b

Please sign in to comment.