Skip to content

Commit

Permalink
feat: landscape orientation
Browse files Browse the repository at this point in the history
  • Loading branch information
thenick775 committed Dec 16, 2024
1 parent afcedc2 commit 7d2db54
Show file tree
Hide file tree
Showing 12 changed files with 781 additions and 520 deletions.
982 changes: 510 additions & 472 deletions gbajs3/src/components/controls/__snapshots__/control-panel.spec.tsx.snap

Large diffs are not rendered by default.

161 changes: 126 additions & 35 deletions gbajs3/src/components/controls/control-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { IconButton, Slider, useMediaQuery } from '@mui/material';
import {
ClickAwayListener,
IconButton,
Slider,
Tooltip,
tooltipClasses,
useMediaQuery
} from '@mui/material';
import { useLocalStorage } from '@uidotdev/usehooks';
import { useCallback, useId, useState, type ReactNode } from 'react';
import {
useCallback,
useId,
useState,
forwardRef,
type ReactNode
} from 'react';
import { IconContext } from 'react-icons';
import { AiOutlineFastForward, AiOutlineForward } from 'react-icons/ai';
import {
Expand Down Expand Up @@ -36,6 +49,7 @@ import { ButtonBase } from '../shared/custom-button-base.tsx';
import { GripperHandle } from '../shared/gripper-handle.tsx';

import type { IconButtonProps, SliderProps } from '@mui/material';
import type { IconType } from 'react-icons';

type PanelProps = {
$controlled: boolean;
Expand All @@ -61,6 +75,10 @@ type PanelSliderProps = {
minIcon: ReactNode;
} & SliderProps;

type TooltipSliderProps = PanelSliderProps & {
ButtonIcon: IconType;
};

type ControlledProps = {
$controlled: boolean;
};
Expand Down Expand Up @@ -138,6 +156,11 @@ const PanelControlButton = styled(ButtonBase).attrs({
&:active {
color: ${({ theme }) => theme.gbaThemeBlue};
}
@media ${({ theme }) => theme.isMobileLandscape} {
flex-shrink: 1;
min-width: unset;
}
`;

const PanelControlSlider = styled.li<PanelControlSliderProps>`
Expand All @@ -155,6 +178,10 @@ const MutedMarkSlider = styled(Slider)`
}
`;

const ContentSpan = styled.span`
display: contents;
`;

const PanelButton = ({
ariaLabel,
children,
Expand Down Expand Up @@ -194,29 +221,83 @@ const SliderIconButton = ({ icon, ...rest }: SliderIconButtonProps) => {
);
};

const PanelSlider = ({
controlled,
gridArea,
id,
maxIcon,
minIcon,
...rest
}: PanelSliderProps) => {
return (
<PanelControlSlider id={id} $gridArea={gridArea} $controlled={controlled}>
{minIcon}
<MutedMarkSlider
marks
sx={{
width: '85px',
margin: '0 10px',
maxHeight: '40px'
}}
valueLabelDisplay="auto"
{...rest}
/>
{maxIcon}
</PanelControlSlider>
const PanelSlider = forwardRef<HTMLSpanElement, PanelSliderProps>(
({ controlled, gridArea, id, maxIcon, minIcon, ...rest }, ref) => {
return (
<ContentSpan ref={ref}>
<PanelControlSlider
id={id}
$gridArea={gridArea}
$controlled={controlled}
>
{minIcon}
<MutedMarkSlider
marks
sx={{
width: '85px',
margin: '0 10px',
maxHeight: '40px'
}}
valueLabelDisplay="auto"
{...rest}
/>
{maxIcon}
</PanelControlSlider>
</ContentSpan>
);
}
);

const popperStyles = {
[`&.${tooltipClasses.popper}[data-popper-placement*="bottom"] .${tooltipClasses.tooltip}`]:
{
marginTop: '16px'
},
[`&.${tooltipClasses.popper}[data-popper-placement*="top"] .${tooltipClasses.tooltip}`]:
{
marginBottom: '16px'
},
[`&.${tooltipClasses.popper}[data-popper-placement*="right"] .${tooltipClasses.tooltip}`]:
{
marginLeft: '16px'
},
[`&.${tooltipClasses.popper}[data-popper-placement*="left"] .${tooltipClasses.tooltip}`]:
{
marginRight: '16px'
}
};

const TooltipPanelSlider = ({ ButtonIcon, ...rest }: TooltipSliderProps) => {
const theme = useTheme();
const isMobileLandscape = useMediaQuery(theme.isMobileLandscape);
const [isTooltipOpen, setIsTooltipOpen] = useState(false);

return isMobileLandscape ? (
<Tooltip
open={isTooltipOpen}
title={
<ClickAwayListener onClickAway={() => setIsTooltipOpen(false)}>
<PanelSlider {...rest} />
</ClickAwayListener>
}
arrow
slotProps={{
popper: {
sx: popperStyles
},
tooltip: { sx: { padding: '8px 16px' } }
}}
placement="bottom-end"
>
<PanelControlButton
onClick={() => setIsTooltipOpen((prevState) => !prevState)}
$controlled={rest.controlled}
>
<ButtonIcon style={{ maxHeight: '100%' }} />
</PanelControlButton>
</Tooltip>
) : (
<PanelSlider {...rest} />
);
};

Expand All @@ -228,6 +309,7 @@ export const ControlPanel = () => {
const { layouts, setLayout } = useLayoutContext();
const theme = useTheme();
const isLargerThanPhone = useMediaQuery(theme.isLargerThanPhone);
const isMobileLandscape = useMediaQuery(theme.isMobileLandscape);
const [isPaused, setIsPaused] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const controlPanelId = useId();
Expand Down Expand Up @@ -362,14 +444,21 @@ export const ControlPanel = () => {
}
];

const defaultPosition = {
x: Math.floor(canvasBounds.left),
y: Math.floor(canvasBounds.bottom + dragWrapperPadding)
};
const defaultSize = {
width: isLargerThanPhone ? 'auto' : '100dvw',
height: 'auto'
};
const defaultPosition = isMobileLandscape
? { x: Math.floor(canvasBounds.left + canvasBounds.width), y: 0 }
: {
x: Math.floor(canvasBounds.left),
y: Math.floor(canvasBounds.bottom + dragWrapperPadding)
};
const defaultSize = isMobileLandscape
? {
width: Math.min(60, canvasBounds.left),
height: 'auto'
}
: {
width: isLargerThanPhone ? 'auto' : '100dvw',
height: 'auto'
};

const position = layouts?.controlPanel?.position ?? defaultPosition;
const size = layouts?.controlPanel?.size ?? defaultSize;
Expand Down Expand Up @@ -471,7 +560,7 @@ export const ControlPanel = () => {
<TbResize />
)}
</PanelButton>
<PanelSlider
<TooltipPanelSlider
id={`${controlPanelId}--volume-slider`}
aria-label="Volume Slider"
gridArea="volume"
Expand All @@ -496,9 +585,10 @@ export const ControlPanel = () => {
}
valueLabelFormat={`${currentEmulatorVolume * 100}`}
onChange={setVolumeFromEvent}
ButtonIcon={BiVolumeFull}
{...defaultSliderEvents}
/>
<PanelSlider
<TooltipPanelSlider
id={`${controlPanelId}--fast-forward`}
aria-label="Fast Forward Slider"
gridArea="fastForward"
Expand All @@ -523,6 +613,7 @@ export const ControlPanel = () => {
}
valueLabelFormat={`x${fastForwardMultiplier}`}
onChange={setFastForwardFromEvent}
ButtonIcon={AiOutlineFastForward}
{...defaultSliderEvents}
/>
</IconContext.Provider>
Expand Down
8 changes: 8 additions & 0 deletions gbajs3/src/components/controls/o-pad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ const BackgroundContainer = styled.section<BackgroundContainerProps>`
top: ${$initialPosition.top};
left: ${$initialPosition.left};
`};
@media ${({ theme }) => theme.isMobileLandscape} {
background-color: transparent;
}
`;

const CenterKnob = styled.div<CenterKnobProps>`
Expand All @@ -87,6 +91,10 @@ const CenterKnob = styled.div<CenterKnobProps>`
border: 0.8rem solid ${({ theme }) => theme.gbaThemeBlue}50;
border-radius: 50%;
}
@media ${({ theme }) => theme.isMobileLandscape} {
background-color: transparent;
}
`;

const DirectionArrow = styled.div`
Expand Down
4 changes: 4 additions & 0 deletions gbajs3/src/components/controls/virtual-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ const VirtualButtonBase = styled(ButtonBase)`
cursor: pointer;
box-sizing: content-box;
border-width: 2px;
@media ${({ theme }) => theme.isMobileLandscape} {
background-color: transparent;
}
`;

const CircularButton = styled(VirtualButtonBase)<CircularButtonProps>`
Expand Down
Loading

0 comments on commit 7d2db54

Please sign in to comment.