mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add tooltip for tabs (#7482)
This commit is contained in:
@@ -330,7 +330,7 @@ const CourseSection = ({
|
||||
style={styles.desktopTableOfContent}
|
||||
>
|
||||
<Text noMargin size="sub-title">
|
||||
Chapters
|
||||
<Trans>Chapters</Trans>
|
||||
</Text>
|
||||
{courseCompletion !== null && (
|
||||
<Line noMargin>
|
||||
|
@@ -33,9 +33,10 @@ type DraggableEditorTabsProps = {|
|
||||
onCloseAll: () => void,
|
||||
onTabActivated: (editor: EditorTab) => void,
|
||||
onDropTab: (fromIndex: number, toHoveredIndex: number) => void,
|
||||
onHoverTab: (editor: ?EditorTab) => void,
|
||||
|};
|
||||
|
||||
const getTabId = (editorTab: EditorTab) =>
|
||||
export const getTabId = (editorTab: EditorTab) =>
|
||||
`tab-${editorTab.key.replace(/\s/g, '-')}`;
|
||||
|
||||
const homeTabApproximateWidth = 35;
|
||||
@@ -49,6 +50,7 @@ export function DraggableEditorTabs({
|
||||
onCloseAll,
|
||||
onTabActivated,
|
||||
onDropTab,
|
||||
onHoverTab,
|
||||
}: DraggableEditorTabsProps) {
|
||||
let draggedTabIndex: ?number = null;
|
||||
|
||||
@@ -108,6 +110,7 @@ export function DraggableEditorTabs({
|
||||
onClose={() => onCloseTab(editorTab)}
|
||||
onCloseOthers={() => onCloseOtherTabs(editorTab)}
|
||||
onCloseAll={onCloseAll}
|
||||
onHover={(enter: boolean) => onHoverTab(enter ? editorTab : null)}
|
||||
onActivated={() => onTabActivated(editorTab)}
|
||||
closable={editorTab.closable}
|
||||
onBeginDrag={() => {
|
||||
@@ -152,6 +155,7 @@ export function DraggableClosableTab({
|
||||
onActivated,
|
||||
onBeginDrag,
|
||||
onDrop,
|
||||
onHover,
|
||||
maxWidth,
|
||||
}: DraggableClosableTabProps) {
|
||||
return (
|
||||
@@ -190,6 +194,7 @@ export function DraggableClosableTab({
|
||||
renderCustomIcon={renderCustomIcon}
|
||||
closable={closable}
|
||||
onClick={onClick}
|
||||
onHover={onHover}
|
||||
onActivated={onActivated}
|
||||
maxWidth={maxWidth}
|
||||
key={id}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
import MenuIcon from '../UI/CustomSvgIcons/Menu';
|
||||
import IconButton from '../UI/IconButton';
|
||||
import GDevelopThemeContext from '../UI/Theme/GDevelopThemeContext';
|
||||
@@ -8,6 +9,10 @@ import {
|
||||
TitleBarLeftSafeMargins,
|
||||
TitleBarRightSafeMargins,
|
||||
} from '../UI/TitleBarSafeMargins';
|
||||
import { type EditorTab } from './EditorTabs/EditorTabsHandler';
|
||||
import { getTabId } from './EditorTabs/DraggableEditorTabs';
|
||||
import { useScreenType } from '../UI/Responsive/ScreenTypeMeasurer';
|
||||
import TabsTitlebarTooltip from './TabsTitlebarTooltip';
|
||||
|
||||
const WINDOW_DRAGGABLE_PART_CLASS_NAME = 'title-bar-draggable-part';
|
||||
const WINDOW_NON_DRAGGABLE_PART_CLASS_NAME = 'title-bar-non-draggable-part';
|
||||
@@ -30,21 +35,27 @@ const styles = {
|
||||
};
|
||||
|
||||
type TabsTitlebarProps = {|
|
||||
children: React.Node,
|
||||
hidden: boolean,
|
||||
toggleProjectManager: () => void,
|
||||
renderTabs: (onHoverEditorTab: (?EditorTab) => void) => React.Node,
|
||||
|};
|
||||
|
||||
/**
|
||||
* The titlebar containing a menu, the tabs and giving space for window controls.
|
||||
*/
|
||||
export default function TabsTitlebar({
|
||||
children,
|
||||
toggleProjectManager,
|
||||
hidden,
|
||||
renderTabs,
|
||||
}: TabsTitlebarProps) {
|
||||
const isTouchscreen = useScreenType() === 'touch';
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const backgroundColor = gdevelopTheme.titlebar.backgroundColor;
|
||||
const [tooltipData, setTooltipData] = React.useState<?{|
|
||||
element: HTMLElement,
|
||||
editorTab: EditorTab,
|
||||
|}>(null);
|
||||
const tooltipTimeoutId = React.useRef<?TimeoutID>(null);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
@@ -53,6 +64,51 @@ export default function TabsTitlebar({
|
||||
[backgroundColor]
|
||||
);
|
||||
|
||||
const onHoverEditorTab = React.useCallback(
|
||||
(editorTab: ?EditorTab) => {
|
||||
if (isTouchscreen) {
|
||||
setTooltipData(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tooltipTimeoutId.current) {
|
||||
clearTimeout(tooltipTimeoutId.current);
|
||||
tooltipTimeoutId.current = null;
|
||||
}
|
||||
|
||||
if (editorTab) {
|
||||
const element = document.getElementById(getTabId(editorTab));
|
||||
if (element) {
|
||||
tooltipTimeoutId.current = setTimeout(
|
||||
() => {
|
||||
setTooltipData({ editorTab, element });
|
||||
},
|
||||
// If the tooltip is already displayed, quickly change to the new tab
|
||||
// but not too quick because the display might look flickering.
|
||||
tooltipData ? 100 : 500
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tooltipTimeoutId.current = setTimeout(() => {
|
||||
setTooltipData(null);
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
[isTouchscreen, tooltipData]
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
return () => {
|
||||
if (tooltipTimeoutId.current) {
|
||||
clearTimeout(tooltipTimeoutId.current);
|
||||
}
|
||||
};
|
||||
},
|
||||
// Clear timeout if necessary when unmounting.
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -77,8 +133,14 @@ export default function TabsTitlebar({
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
{children}
|
||||
{renderTabs(onHoverEditorTab)}
|
||||
<TitleBarRightSafeMargins />
|
||||
{tooltipData && (
|
||||
<TabsTitlebarTooltip
|
||||
anchorElement={tooltipData.element}
|
||||
editorTab={tooltipData.editorTab}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
162
newIDE/app/src/MainFrame/TabsTitlebarTooltip.js
Normal file
162
newIDE/app/src/MainFrame/TabsTitlebarTooltip.js
Normal file
@@ -0,0 +1,162 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Fade from '@material-ui/core/Fade';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Popper from '@material-ui/core/Popper';
|
||||
|
||||
import GDevelopThemeContext from '../UI/Theme/GDevelopThemeContext';
|
||||
import {
|
||||
getEditorTabMetadata,
|
||||
type EditorTab,
|
||||
type EditorKind,
|
||||
} from './EditorTabs/EditorTabsHandler';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
import { Line } from '../UI/Grid';
|
||||
import Text from '../UI/Text';
|
||||
|
||||
const editorKindToLabel: { [kind: EditorKind]: React.Node } = {
|
||||
layout: <Trans>Scene</Trans>,
|
||||
'layout events': <Trans>Scene events</Trans>,
|
||||
'external layout': <Trans>External layout</Trans>,
|
||||
'external events': <Trans>External events</Trans>,
|
||||
'events functions extension': <Trans>Extension</Trans>,
|
||||
'custom object': <Trans>Object</Trans>,
|
||||
debugger: <Trans>Debugger</Trans>,
|
||||
resources: <Trans>Resources</Trans>,
|
||||
'start page': <Trans>Homepage</Trans>,
|
||||
};
|
||||
|
||||
const styles = {
|
||||
paper: {
|
||||
padding: '8px 10px',
|
||||
minWidth: 180,
|
||||
},
|
||||
tabIcon: {
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
display: 'flex',
|
||||
},
|
||||
emptyTabIcon: {
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
height: 20,
|
||||
width: 24,
|
||||
display: 'flex',
|
||||
},
|
||||
tooltip: {
|
||||
zIndex: 3,
|
||||
maxWidth: 'min(90%, 300px)',
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
anchorElement: HTMLElement,
|
||||
editorTab: EditorTab,
|
||||
|};
|
||||
|
||||
const TabsTitlebarTooltip = ({ anchorElement, editorTab }: Props) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const [tooltipStyle, setTooltipStyle] = React.useState<Object>(
|
||||
styles.tooltip
|
||||
);
|
||||
|
||||
const brightness = gdevelopTheme.palette.type === 'dark' ? 0.978 : 0.224;
|
||||
|
||||
const editorTabMetadata = getEditorTabMetadata(editorTab);
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
setTooltipStyle(currentStyle => ({
|
||||
...currentStyle,
|
||||
transition: 'transform 150ms ease-in-out',
|
||||
}));
|
||||
}, 100);
|
||||
return () => clearTimeout(timeoutId);
|
||||
},
|
||||
// Apply transition after first display of tooltip to avoid having the
|
||||
// transition weirdly applied from the 0;0 coordinates.
|
||||
[]
|
||||
);
|
||||
|
||||
const title = editorKindToLabel[editorTabMetadata.editorKind];
|
||||
let subtitle = null;
|
||||
if (
|
||||
[
|
||||
'layout',
|
||||
'layout events',
|
||||
'external layout',
|
||||
'external events',
|
||||
'events functions extension',
|
||||
].includes(editorTabMetadata.editorKind)
|
||||
) {
|
||||
subtitle = editorTabMetadata.projectItemName;
|
||||
} else if (
|
||||
editorTabMetadata.editorKind === 'custom object' &&
|
||||
editorTabMetadata.projectItemName
|
||||
) {
|
||||
const nameParts = editorTabMetadata.projectItemName.split('::');
|
||||
const customObjectName = nameParts[1];
|
||||
if (customObjectName) {
|
||||
subtitle = customObjectName;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Popper
|
||||
id="tabs-titlebar-tooltip"
|
||||
open={true}
|
||||
anchorEl={anchorElement}
|
||||
transition
|
||||
placement={'bottom-start'}
|
||||
popperOptions={{
|
||||
modifiers: {
|
||||
arrow: { enabled: false },
|
||||
offset: {
|
||||
enabled: true,
|
||||
offset: '0,5',
|
||||
},
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
boundariesElement: document.querySelector('.main-frame'),
|
||||
},
|
||||
},
|
||||
}}
|
||||
style={tooltipStyle}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Fade {...TransitionProps} timeout={{ enter: 350, exit: 0 }}>
|
||||
<Paper
|
||||
style={{
|
||||
...styles.paper,
|
||||
backgroundColor: gdevelopTheme.paper.backgroundColor.light,
|
||||
}}
|
||||
elevation={4}
|
||||
>
|
||||
<ColumnStackLayout noMargin>
|
||||
<Line alignItems="center" noMargin>
|
||||
{editorTab.icon || editorTab.renderCustomIcon ? (
|
||||
<span style={styles.tabIcon}>
|
||||
{editorTab.renderCustomIcon
|
||||
? editorTab.renderCustomIcon(brightness)
|
||||
: editorTab.icon}
|
||||
</span>
|
||||
) : null}
|
||||
<Text noMargin>{title}</Text>
|
||||
</Line>
|
||||
{subtitle && (
|
||||
<Line alignItems="center" noMargin>
|
||||
<span style={styles.emptyTabIcon} />
|
||||
<Text noMargin>{subtitle}</Text>
|
||||
</Line>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
</Paper>
|
||||
</Fade>
|
||||
)}
|
||||
</Popper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabsTitlebarTooltip;
|
@@ -3697,22 +3697,24 @@ const MainFrame = (props: Props) => {
|
||||
<TabsTitlebar
|
||||
hidden={tabsTitleBarAndEditorToolbarHidden}
|
||||
toggleProjectManager={toggleProjectManager}
|
||||
>
|
||||
<DraggableEditorTabs
|
||||
hideLabels={false}
|
||||
editorTabs={state.editorTabs}
|
||||
onClickTab={(id: number) => _onChangeEditorTab(id)}
|
||||
onCloseTab={(editorTab: EditorTab) => _onCloseEditorTab(editorTab)}
|
||||
onCloseOtherTabs={(editorTab: EditorTab) =>
|
||||
_onCloseOtherEditorTabs(editorTab)
|
||||
}
|
||||
onCloseAll={_onCloseAllEditorTabs}
|
||||
onTabActivated={(editorTab: EditorTab) =>
|
||||
_onEditorTabActivated(editorTab)
|
||||
}
|
||||
onDropTab={onDropEditorTab}
|
||||
/>
|
||||
</TabsTitlebar>
|
||||
renderTabs={onHoverEditorTab => (
|
||||
<DraggableEditorTabs
|
||||
hideLabels={false}
|
||||
editorTabs={state.editorTabs}
|
||||
onClickTab={(id: number) => _onChangeEditorTab(id)}
|
||||
onCloseTab={(editorTab: EditorTab) => _onCloseEditorTab(editorTab)}
|
||||
onCloseOtherTabs={(editorTab: EditorTab) =>
|
||||
_onCloseOtherEditorTabs(editorTab)
|
||||
}
|
||||
onCloseAll={_onCloseAllEditorTabs}
|
||||
onTabActivated={(editorTab: EditorTab) =>
|
||||
_onEditorTabActivated(editorTab)
|
||||
}
|
||||
onDropTab={onDropEditorTab}
|
||||
onHoverTab={onHoverEditorTab}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Toolbar
|
||||
ref={toolbar}
|
||||
hidden={tabsTitleBarAndEditorToolbarHidden}
|
||||
|
@@ -140,6 +140,7 @@ export type ClosableTabProps = {|
|
||||
onCloseAll: () => void,
|
||||
onClick: () => void,
|
||||
onActivated: () => void,
|
||||
onHover: boolean => void,
|
||||
maxWidth: number,
|
||||
|};
|
||||
|
||||
@@ -156,6 +157,7 @@ export function ClosableTab({
|
||||
closable,
|
||||
onClick,
|
||||
onActivated,
|
||||
onHover,
|
||||
maxWidth,
|
||||
}: ClosableTabProps) {
|
||||
React.useEffect(
|
||||
@@ -248,6 +250,8 @@ export function ClosableTab({
|
||||
// A tab lives in the top bar, which has the ability to drag the app window.
|
||||
// Ensure the tab does not have this ability, as it can be dragged itself.
|
||||
className={WINDOW_NON_DRAGGABLE_PART_CLASS_NAME}
|
||||
onMouseEnter={() => onHover(true)}
|
||||
onMouseLeave={() => onHover(false)}
|
||||
>
|
||||
<ButtonBase
|
||||
onClick={onClick}
|
||||
@@ -281,7 +285,6 @@ export function ClosableTab({
|
||||
...styles.tabLabel,
|
||||
maxWidth: labelMaxWidth,
|
||||
}}
|
||||
title={label}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
|
@@ -36,6 +36,7 @@ export const ThreeTabs = () => (
|
||||
<>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable={false}
|
||||
active={value === 0}
|
||||
onClick={() => onChange(0)}
|
||||
@@ -48,6 +49,7 @@ export const ThreeTabs = () => (
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 1}
|
||||
onClick={() => onChange(1)}
|
||||
@@ -60,6 +62,7 @@ export const ThreeTabs = () => (
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 2}
|
||||
onClick={() => onChange(2)}
|
||||
@@ -119,6 +122,7 @@ export const LongLabels = () => (
|
||||
<>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 0}
|
||||
label="Tab 1 with a very very long label"
|
||||
@@ -131,6 +135,7 @@ export const LongLabels = () => (
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 1}
|
||||
onClick={() => onChange(1)}
|
||||
@@ -143,6 +148,7 @@ export const LongLabels = () => (
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 2}
|
||||
onClick={() => onChange(2)}
|
||||
@@ -155,6 +161,7 @@ export const LongLabels = () => (
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 4 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 3}
|
||||
onClick={() => onChange(3)}
|
||||
@@ -232,6 +239,7 @@ export const WithObjectsList = () => (
|
||||
renderTabs={({ containerWidth }) => [
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 0}
|
||||
label="Tab 1"
|
||||
@@ -244,6 +252,7 @@ export const WithObjectsList = () => (
|
||||
/>,
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 1}
|
||||
label="Tab 2"
|
||||
@@ -256,6 +265,7 @@ export const WithObjectsList = () => (
|
||||
/>,
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
onHover={action('onHover')}
|
||||
closable
|
||||
active={value === 2}
|
||||
label="Tab 3"
|
||||
|
Reference in New Issue
Block a user