mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Rework events Drag&Drop in events sheet (#3936)
This commit is contained in:
@@ -160,6 +160,23 @@
|
||||
"frame-border-color": {
|
||||
"value": "#000000"
|
||||
}
|
||||
},
|
||||
"drop-indicator": {
|
||||
"can-drop": {
|
||||
"color": {
|
||||
"value": "{gdevelop.color.dark-blue.value}"
|
||||
}
|
||||
},
|
||||
"cannot-drop": {
|
||||
"color": {
|
||||
"value": "#ff5c5c"
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"color": {
|
||||
"value": "{theme.surface.canvas.background-color.value}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -4,15 +4,28 @@ import * as React from 'react';
|
||||
import { Line, Column } from '../../UI/Grid';
|
||||
import ElementWithMenu from '../../UI/Menu/ElementWithMenu';
|
||||
import { enumerateEventsMetadata } from '../EnumerateEventsMetadata';
|
||||
import { type DropTargetComponent } from '../../UI/DragAndDrop/DropTarget';
|
||||
import { type SortableTreeNode } from '.';
|
||||
import { moveEventToEventsList } from './helpers';
|
||||
import GDevelopThemeContext from '../../UI/Theme/ThemeContext';
|
||||
|
||||
const styles = {
|
||||
addButton: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
dropIndicator: {
|
||||
border: '4px solid black',
|
||||
outline: '1px solid white',
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
onAddEvent: (eventType: string) => void,
|
||||
|
||||
// Connect a drop target to be able to drop an event at the end of the sheet.
|
||||
DnDComponent: DropTargetComponent<SortableTreeNode>,
|
||||
draggedNode: ?SortableTreeNode,
|
||||
rootEventsList: gdEventsList,
|
||||
|};
|
||||
|
||||
const makeMenuTemplateBuilderForEvents = (
|
||||
@@ -25,33 +38,72 @@ const makeMenuTemplateBuilderForEvents = (
|
||||
};
|
||||
});
|
||||
|
||||
export default function BottomButtons({ onAddEvent }: Props) {
|
||||
export default function BottomButtons({
|
||||
onAddEvent,
|
||||
DnDComponent,
|
||||
draggedNode,
|
||||
rootEventsList,
|
||||
}: Props) {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const onDrop = () => {
|
||||
draggedNode &&
|
||||
moveEventToEventsList({
|
||||
targetEventsList: rootEventsList,
|
||||
movingEvent: draggedNode.event,
|
||||
initialEventsList: draggedNode.eventsList,
|
||||
// Drops node at the end of root events list.
|
||||
toIndex: -1,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Column>
|
||||
<Line justifyContent="space-between">
|
||||
<ElementWithMenu
|
||||
openMenuWithSecondaryClick
|
||||
element={
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={() => onAddEvent('BuiltinCommonInstructions::Standard')}
|
||||
id="add-event-button"
|
||||
>
|
||||
<Trans>Add a new event</Trans>
|
||||
</button>
|
||||
}
|
||||
buildMenuTemplate={makeMenuTemplateBuilderForEvents(onAddEvent)}
|
||||
/>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<button style={styles.addButton} className="add-link">
|
||||
<Trans>Add...</Trans>
|
||||
</button>
|
||||
}
|
||||
buildMenuTemplate={makeMenuTemplateBuilderForEvents(onAddEvent)}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
<DnDComponent canDrop={() => true} drop={onDrop}>
|
||||
{({ connectDropTarget, isOver }) =>
|
||||
connectDropTarget(
|
||||
<div>
|
||||
{isOver && (
|
||||
<div
|
||||
style={{
|
||||
...styles.dropIndicator,
|
||||
borderColor: gdevelopTheme.dropIndicator.canDrop,
|
||||
outlineColor: gdevelopTheme.dropIndicator.border,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Column>
|
||||
<Line justifyContent="space-between">
|
||||
<ElementWithMenu
|
||||
openMenuWithSecondaryClick
|
||||
element={
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={() =>
|
||||
onAddEvent('BuiltinCommonInstructions::Standard')
|
||||
}
|
||||
id="add-event-button"
|
||||
>
|
||||
<Trans>Add a new event</Trans>
|
||||
</button>
|
||||
}
|
||||
buildMenuTemplate={makeMenuTemplateBuilderForEvents(
|
||||
onAddEvent
|
||||
)}
|
||||
/>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<button style={styles.addButton} className="add-link">
|
||||
<Trans>Add...</Trans>
|
||||
</button>
|
||||
}
|
||||
buildMenuTemplate={makeMenuTemplateBuilderForEvents(
|
||||
onAddEvent
|
||||
)}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</DnDComponent>
|
||||
);
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ export const eventsTreeWithSearchResults = 'with-search-results';
|
||||
|
||||
export const dropIndicator = 'drop-indicator';
|
||||
export const cantDropIndicator = 'cant-drop-indicator';
|
||||
export const handle = 'move-handle';
|
||||
|
||||
export const linkContainer = 'link-container';
|
||||
|
||||
|
327
newIDE/app/src/EventsSheet/EventsTree/DropContainer.js
Normal file
327
newIDE/app/src/EventsSheet/EventsTree/DropContainer.js
Normal file
@@ -0,0 +1,327 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { getIndentWidth, type SortableTreeNode } from '.';
|
||||
import {
|
||||
moveNodeAbove,
|
||||
moveNodeBelow,
|
||||
moveNodeAsSubEvent,
|
||||
isSameDepthAndJustBelow,
|
||||
} from './helpers';
|
||||
import { type WidthType } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import './style.css';
|
||||
import GDevelopThemeContext from '../../UI/Theme/ThemeContext';
|
||||
import { type DropTargetComponent } from '../../UI/DragAndDrop/DropTarget';
|
||||
|
||||
const sharedStyles = {
|
||||
dropArea: { zIndex: 1, position: 'absolute' },
|
||||
dropIndicator: {
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
border: '4px solid black',
|
||||
outline: '1px solid white',
|
||||
},
|
||||
autoScroll: {
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
height: '10%',
|
||||
zIndex: 2,
|
||||
},
|
||||
};
|
||||
|
||||
type DropTargetContainerStyle = {|
|
||||
left?: string,
|
||||
right?: string,
|
||||
top?: string,
|
||||
bottom?: string,
|
||||
width?: number,
|
||||
height?: number,
|
||||
|};
|
||||
|
||||
type TargetPositionStyles = { [position: string]: DropTargetContainerStyle };
|
||||
|
||||
const getTargetPositionStyles = (
|
||||
indentWidth: number,
|
||||
draggedNodeHeight: number,
|
||||
isDraggedNodeChild: boolean
|
||||
): TargetPositionStyles => ({
|
||||
'bottom-left': { left: '0px', bottom: '0px', top: '50%', width: indentWidth },
|
||||
'bottom-right': {
|
||||
left: `${indentWidth}px`,
|
||||
right: '0px',
|
||||
bottom: '0px',
|
||||
top: '50%',
|
||||
},
|
||||
top: { left: '0px', right: '0px', top: '0px', bottom: '50%' },
|
||||
bottom: { left: '0px', right: '0px', top: '50%', bottom: '0px' },
|
||||
'below-left': {
|
||||
left: isDraggedNodeChild ? '0px' : '10px',
|
||||
top: '100%',
|
||||
height: draggedNodeHeight,
|
||||
width: indentWidth,
|
||||
},
|
||||
'below-right': {
|
||||
left: isDraggedNodeChild ? `${indentWidth + 10}px` : `${indentWidth}px`,
|
||||
right: '0px',
|
||||
top: '100%',
|
||||
height: draggedNodeHeight,
|
||||
},
|
||||
});
|
||||
|
||||
const getIndicatorPositionStyles = (
|
||||
indentWidth: number
|
||||
): TargetPositionStyles => ({
|
||||
bottom: { left: '0px', right: '0px', bottom: '-4px' },
|
||||
'bottom-right': { left: `${indentWidth}px`, right: '0px', bottom: '-4px' },
|
||||
top: { left: '0px', right: '0px', top: '-4px' },
|
||||
});
|
||||
|
||||
function DropTargetContainer({
|
||||
DnDComponent,
|
||||
canDrop,
|
||||
onDrop,
|
||||
style,
|
||||
}: {|
|
||||
DnDComponent: any,
|
||||
canDrop: () => boolean,
|
||||
onDrop: () => void,
|
||||
style: {
|
||||
dropIndicator: DropTargetContainerStyle,
|
||||
dropArea: DropTargetContainerStyle,
|
||||
},
|
||||
|}) {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
return (
|
||||
<DnDComponent canDrop={canDrop} drop={onDrop}>
|
||||
{({ isOver, connectDropTarget, canDrop }) => {
|
||||
return connectDropTarget(
|
||||
<div>
|
||||
{/* Drop area */}
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles.dropArea,
|
||||
...style.dropArea,
|
||||
|
||||
// Uncomment for debugging purposes.
|
||||
// backgroundColor: 'lightblue',
|
||||
// opacity: isOver ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
{/* Drop indicator */}
|
||||
{canDrop && isOver && (
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles.dropIndicator,
|
||||
...style.dropIndicator,
|
||||
borderColor: gdevelopTheme.dropIndicator.canDrop,
|
||||
outlineColor: gdevelopTheme.dropIndicator.border,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</DnDComponent>
|
||||
);
|
||||
}
|
||||
|
||||
type DropContainerProps = {|
|
||||
node: SortableTreeNode,
|
||||
draggedNode: SortableTreeNode,
|
||||
DnDComponent: DropTargetComponent<SortableTreeNode>,
|
||||
onDrop: (
|
||||
moveFunction: ({
|
||||
targetNode: SortableTreeNode,
|
||||
node: SortableTreeNode,
|
||||
}) => void,
|
||||
node: SortableTreeNode
|
||||
) => void,
|
||||
activateTargets: boolean,
|
||||
|
||||
// Computes drop areas and drop indicator indent.
|
||||
windowWidth: WidthType,
|
||||
// Used only for the node just above dragged node if it is an only child,
|
||||
// so that drop area covers the whole dragged node's row in height.
|
||||
draggedNodeHeight: number,
|
||||
|};
|
||||
|
||||
/**
|
||||
* DropContainer is composed of sub-containers of drop targets that allows us to identify
|
||||
* where the mouse or touch is and drop the dragged node accordingly. At most, there will be 5
|
||||
* drop targets: 1 at the top of the row (drop above), 2 at the bottom (drop below or as subevent
|
||||
* while hovering target node), 2 below (drop below or as sub-event moving left/right).
|
||||
*/
|
||||
export function DropContainer({
|
||||
node,
|
||||
draggedNode,
|
||||
DnDComponent,
|
||||
onDrop,
|
||||
activateTargets,
|
||||
windowWidth,
|
||||
draggedNodeHeight,
|
||||
}: DropContainerProps) {
|
||||
const isDraggedNodesOnlyChild =
|
||||
node.children.length === 1 && node.children[0].key === draggedNode.key;
|
||||
const isDraggedNodeSameDepthAndBelow = isSameDepthAndJustBelow(
|
||||
node,
|
||||
draggedNode
|
||||
);
|
||||
// We want to allow dropping below if the event has no children OR if the only
|
||||
// child of the event is the dragged one.
|
||||
const canDropBelow =
|
||||
!!node.event && (node.children.length === 0 || isDraggedNodesOnlyChild);
|
||||
const canHaveSubEvents = !!node.event && node.event.canHaveSubEvents();
|
||||
|
||||
const indentWidth = getIndentWidth(windowWidth);
|
||||
const dropAreaStyles = getTargetPositionStyles(
|
||||
indentWidth,
|
||||
draggedNodeHeight,
|
||||
isDraggedNodesOnlyChild
|
||||
);
|
||||
const indicatorStyles = getIndicatorPositionStyles(indentWidth);
|
||||
const commonProps = {
|
||||
DnDComponent: DnDComponent,
|
||||
canDrop: () => true,
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
visibility: activateTargets ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['top'],
|
||||
dropArea: dropAreaStyles['top'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeAbove, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
{canHaveSubEvents && canDropBelow && (
|
||||
<>
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['bottom-right'],
|
||||
dropArea: dropAreaStyles['bottom-right'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeAsSubEvent, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['bottom'],
|
||||
dropArea: dropAreaStyles['bottom-left'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeBelow, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
{/* Allow dragging left/right to move below or as subevent */}
|
||||
{(isDraggedNodesOnlyChild || isDraggedNodeSameDepthAndBelow) && (
|
||||
<>
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['bottom'],
|
||||
dropArea: dropAreaStyles['below-left'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeBelow, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['bottom-right'],
|
||||
dropArea: dropAreaStyles['below-right'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeAsSubEvent, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!canHaveSubEvents && canDropBelow && (
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['bottom'],
|
||||
dropArea: dropAreaStyles['bottom'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeBelow, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
)}
|
||||
{canHaveSubEvents && !canDropBelow && (
|
||||
<DropTargetContainer
|
||||
style={{
|
||||
dropIndicator: indicatorStyles['bottom-right'],
|
||||
dropArea: dropAreaStyles['bottom'],
|
||||
}}
|
||||
onDrop={() => onDrop(moveNodeAsSubEvent, node)}
|
||||
{...commonProps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AutoScroll({
|
||||
direction,
|
||||
DnDComponent,
|
||||
activateTargets,
|
||||
onHover,
|
||||
}: {|
|
||||
direction: 'top' | 'bottom',
|
||||
DnDComponent: DropTargetComponent<SortableTreeNode>,
|
||||
activateTargets: boolean,
|
||||
onHover: () => void,
|
||||
|}) {
|
||||
const delayActivationTimer = React.useRef<?TimeoutID>(null);
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
// This drop target overlaps with sibling drag source and cancels drag immediately.
|
||||
// See: https://github.com/react-dnd/react-dnd/issues/766#issuecomment-388943403
|
||||
// Delaying the render of the drop target seems to solve the issue.
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (activateTargets) {
|
||||
delayActivationTimer.current = setTimeout(() => {
|
||||
setShow(true);
|
||||
}, 100);
|
||||
} else {
|
||||
setShow(false);
|
||||
clearTimeout(delayActivationTimer.current);
|
||||
delayActivationTimer.current = null;
|
||||
}
|
||||
return () => {
|
||||
delayActivationTimer.current &&
|
||||
clearTimeout(delayActivationTimer.current);
|
||||
};
|
||||
},
|
||||
[activateTargets]
|
||||
);
|
||||
|
||||
return (
|
||||
<DnDComponent
|
||||
canDrop={() => true}
|
||||
drop={() => {
|
||||
return;
|
||||
}}
|
||||
>
|
||||
{({ isOverLazy, connectDropTarget }) => {
|
||||
if (isOverLazy) {
|
||||
onHover();
|
||||
}
|
||||
const dropTarget = (
|
||||
<div
|
||||
style={{
|
||||
...sharedStyles.autoScroll,
|
||||
...(direction === 'top' ? { top: 0 } : { bottom: 0 }),
|
||||
|
||||
// Uncomment for debugging purposes.
|
||||
// backgroundColor: 'black',
|
||||
// opacity: isOverLazy ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return show ? connectDropTarget(dropTarget) : null;
|
||||
}}
|
||||
</DnDComponent>
|
||||
);
|
||||
}
|
89
newIDE/app/src/EventsSheet/EventsTree/helpers.js
Normal file
89
newIDE/app/src/EventsSheet/EventsTree/helpers.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { type SortableTreeNode } from '.';
|
||||
|
||||
export type MoveFunctionArguments = {
|
||||
targetNode: SortableTreeNode,
|
||||
node: SortableTreeNode,
|
||||
};
|
||||
|
||||
export const moveEventToEventsList = ({
|
||||
targetEventsList,
|
||||
movingEvent,
|
||||
initialEventsList,
|
||||
toIndex,
|
||||
}: {
|
||||
targetEventsList: gdEventsList,
|
||||
movingEvent: gdBaseEvent,
|
||||
initialEventsList: gdEventsList,
|
||||
toIndex: number,
|
||||
}) => {
|
||||
initialEventsList.moveEventToAnotherEventsList(
|
||||
movingEvent,
|
||||
targetEventsList,
|
||||
toIndex
|
||||
);
|
||||
};
|
||||
|
||||
export const moveNodeAsSubEvent = ({
|
||||
targetNode,
|
||||
node,
|
||||
}: MoveFunctionArguments) => {
|
||||
if (!targetNode.event || !node.event) return;
|
||||
moveEventToEventsList({
|
||||
targetEventsList: targetNode.event.getSubEvents(),
|
||||
movingEvent: node.event,
|
||||
initialEventsList: node.eventsList,
|
||||
toIndex: 0,
|
||||
});
|
||||
};
|
||||
|
||||
export const moveNodeBelow = ({ targetNode, node }: MoveFunctionArguments) => {
|
||||
if (!targetNode.event || !node.event) return;
|
||||
const toIndex =
|
||||
node.depth === targetNode.depth && node.indexInList < targetNode.indexInList
|
||||
? targetNode.indexInList
|
||||
: targetNode.indexInList + 1;
|
||||
moveEventToEventsList({
|
||||
targetEventsList: targetNode.eventsList,
|
||||
movingEvent: node.event,
|
||||
initialEventsList: node.eventsList,
|
||||
toIndex,
|
||||
});
|
||||
};
|
||||
|
||||
export const moveNodeAbove = ({ targetNode, node }: MoveFunctionArguments) => {
|
||||
if (!targetNode.event || !node.event) return;
|
||||
const toIndex =
|
||||
node.depth === targetNode.depth && node.indexInList < targetNode.indexInList
|
||||
? targetNode.indexInList - 1
|
||||
: targetNode.indexInList;
|
||||
moveEventToEventsList({
|
||||
targetEventsList: targetNode.eventsList,
|
||||
movingEvent: node.event,
|
||||
initialEventsList: node.eventsList,
|
||||
toIndex,
|
||||
});
|
||||
};
|
||||
|
||||
export const isDescendant = (
|
||||
olderNode: SortableTreeNode,
|
||||
childNode: SortableTreeNode
|
||||
) => {
|
||||
if (childNode.depth <= olderNode.depth) return false;
|
||||
const parentPath = olderNode.nodePath;
|
||||
const childPath = childNode.nodePath;
|
||||
return parentPath.every((pathValue, index) => pathValue === childPath[index]);
|
||||
};
|
||||
|
||||
export const isSameDepthAndJustBelow = (
|
||||
aboveNode: SortableTreeNode,
|
||||
belowNode: SortableTreeNode
|
||||
) => {
|
||||
if (aboveNode.depth !== belowNode.depth) return false;
|
||||
const belowNodePath = belowNode.nodePath;
|
||||
const aboveNodePath = aboveNode.nodePath;
|
||||
if (belowNodePath[belowNodePath.length - 1] === 0) return false;
|
||||
return (
|
||||
belowNodePath[belowNodePath.length - 1] - 1 ===
|
||||
aboveNodePath[aboveNodePath.length - 1]
|
||||
);
|
||||
};
|
@@ -2,16 +2,19 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import React, { Component, type Node } from 'react';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import {
|
||||
SortableTreeWithoutDndContext,
|
||||
getNodeAtPath,
|
||||
} from 'react-sortable-tree';
|
||||
import { SortableTreeWithoutDndContext } from 'react-sortable-tree';
|
||||
import { type ConnectDragSource } from 'react-dnd';
|
||||
import { mapFor } from '../../Utils/MapFor';
|
||||
import { getInitialSelection, isEventSelected } from '../SelectionHandler';
|
||||
import EventsRenderingService from './EventsRenderingService';
|
||||
import EventHeightsCache from './EventHeightsCache';
|
||||
import classNames from 'classnames';
|
||||
import { eventsTree, eventsTreeWithSearchResults, icon } from './ClassNames';
|
||||
import {
|
||||
eventsTree,
|
||||
eventsTreeWithSearchResults,
|
||||
handle,
|
||||
icon,
|
||||
} from './ClassNames';
|
||||
import {
|
||||
type SelectionState,
|
||||
type EventContext,
|
||||
@@ -37,8 +40,14 @@ import { type Preferences } from '../../MainFrame/Preferences/PreferencesContext
|
||||
import { type Tutorial } from '../../Utils/GDevelopServices/Tutorial';
|
||||
import TutorialMessage from '../../Hints/TutorialMessage';
|
||||
import getTutorial from '../../Hints/getTutorial';
|
||||
import { makeDragSourceAndDropTarget } from '../../UI/DragAndDrop/DragSourceAndDropTarget';
|
||||
import { makeDropTarget } from '../../UI/DragAndDrop/DropTarget';
|
||||
import { AutoScroll, DropContainer } from './DropContainer';
|
||||
import { isDescendant, type MoveFunctionArguments } from './helpers';
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
const eventsSheetEventsDnDType = 'events-sheet-events-dnd-type';
|
||||
|
||||
const getThumbnail = ObjectsRenderingService.getThumbnail.bind(
|
||||
ObjectsRenderingService
|
||||
);
|
||||
@@ -47,16 +56,24 @@ const defaultIndentWidth = 22;
|
||||
const smallIndentWidth = 11;
|
||||
|
||||
const styles = {
|
||||
container: { flex: 1 },
|
||||
container: { flex: 1, position: 'relative' },
|
||||
defaultEventContainer: {
|
||||
marginRight: 10,
|
||||
position: 'relative',
|
||||
},
|
||||
smallEventContainer: {
|
||||
marginRight: 0,
|
||||
position: 'relative',
|
||||
},
|
||||
eventComponentContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'stretch',
|
||||
position: 'relative',
|
||||
},
|
||||
};
|
||||
|
||||
const getIndentWidth = (windowWidth: WidthType) =>
|
||||
export const getIndentWidth = (windowWidth: WidthType) =>
|
||||
windowWidth === 'small' ? smallIndentWidth : defaultIndentWidth;
|
||||
const getEventContainerStyle = (windowWidth: WidthType) =>
|
||||
windowWidth === 'small'
|
||||
@@ -95,8 +112,9 @@ type EventsContainerProps = {|
|
||||
renderObjectThumbnail: string => Node,
|
||||
|
||||
screenType: ScreenType,
|
||||
windowWidth: WidthType,
|
||||
eventsSheetHeight: number,
|
||||
|
||||
connectDragSource: ConnectDragSource,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -134,46 +152,49 @@ class EventContainer extends Component<EventsContainerProps, {||}> {
|
||||
ref={container => (this._container = container)}
|
||||
onClick={this.props.onEventClick}
|
||||
onContextMenu={this._onEventContextMenu}
|
||||
style={getEventContainerStyle(this.props.windowWidth)}
|
||||
>
|
||||
{EventComponent && (
|
||||
<EventComponent
|
||||
project={project}
|
||||
scope={scope}
|
||||
event={event}
|
||||
globalObjectsContainer={this.props.globalObjectsContainer}
|
||||
objectsContainer={this.props.objectsContainer}
|
||||
selected={isEventSelected(this.props.selection, event)}
|
||||
selection={this.props.selection}
|
||||
leftIndentWidth={this.props.leftIndentWidth}
|
||||
onUpdate={this._onEventUpdated}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onAddInstructionContextMenu={this.props.onAddInstructionContextMenu}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
onOpenExternalEvents={this.props.onOpenExternalEvents}
|
||||
onOpenLayout={this.props.onOpenLayout}
|
||||
disabled={
|
||||
disabled /* Use disabled (not event.disabled) as it is true if a parent event is disabled*/
|
||||
}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
eventsSheetHeight={this.props.eventsSheetHeight}
|
||||
/>
|
||||
<div style={styles.eventComponentContainer}>
|
||||
{this.props.connectDragSource(<div className={handle} />)}
|
||||
<div style={styles.container}>
|
||||
<EventComponent
|
||||
project={project}
|
||||
scope={scope}
|
||||
event={event}
|
||||
globalObjectsContainer={this.props.globalObjectsContainer}
|
||||
objectsContainer={this.props.objectsContainer}
|
||||
selected={isEventSelected(this.props.selection, event)}
|
||||
selection={this.props.selection}
|
||||
leftIndentWidth={this.props.leftIndentWidth}
|
||||
onUpdate={this._onEventUpdated}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onAddInstructionContextMenu={
|
||||
this.props.onAddInstructionContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
onOpenExternalEvents={this.props.onOpenExternalEvents}
|
||||
onOpenLayout={this.props.onOpenLayout}
|
||||
disabled={
|
||||
disabled /* Use disabled (not event.disabled) as it is true if a parent event is disabled*/
|
||||
}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
eventsSheetHeight={this.props.eventsSheetHeight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getNodeKey = ({ treeIndex }) => treeIndex;
|
||||
|
||||
const SortableTree = ({ className, ...otherProps }) => (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => (
|
||||
@@ -238,36 +259,64 @@ type EventsTreeProps = {|
|
||||
|};
|
||||
|
||||
// A node displayed by the SortableTree. Almost always represents an
|
||||
// event, except for the buttons at the bottom of the sheet.
|
||||
type SortableTreeNode = {
|
||||
// event, except for the buttons at the bottom of the sheet and the tutorial.
|
||||
export type SortableTreeNode = {
|
||||
// Necessary attributes for react-sortable-tree.
|
||||
title: (node: { node: SortableTreeNode }) => Node,
|
||||
children: Array<any>,
|
||||
expanded: boolean,
|
||||
|
||||
eventsList: gdEventsList,
|
||||
event: ?gdBaseEvent,
|
||||
depth: number,
|
||||
disabled: boolean,
|
||||
indexInList: number,
|
||||
nodePath: Array<number>,
|
||||
// Key is event pointer or an identification string.
|
||||
key: number | string,
|
||||
|
||||
// In case of nodes without event (buttons at the bottom of the sheet),
|
||||
// use a fixed height.
|
||||
fixedHeight?: ?number,
|
||||
};
|
||||
|
||||
type State = {|
|
||||
treeData: Array<any>,
|
||||
flatData: Array<gdBaseEvent>,
|
||||
draggedNode: ?SortableTreeNode,
|
||||
isScrolledTop: boolean,
|
||||
isScrolledBottom: boolean,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Display a tree of event. Builtin on react-sortable-tree so that event
|
||||
* can be drag'n'dropped and events rows are virtualized.
|
||||
*/
|
||||
export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
export default class ThemableEventsTree extends Component<
|
||||
EventsTreeProps,
|
||||
State
|
||||
> {
|
||||
static defaultProps = {
|
||||
selection: getInitialSelection(),
|
||||
};
|
||||
_list: ?any;
|
||||
eventsHeightsCache: EventHeightsCache;
|
||||
DragSourceAndDropTarget = makeDragSourceAndDropTarget<SortableTreeNode>(
|
||||
eventsSheetEventsDnDType
|
||||
);
|
||||
DropTarget = makeDropTarget<SortableTreeNode>(eventsSheetEventsDnDType);
|
||||
temporaryUnfoldedNodes: Array<SortableTreeNode>;
|
||||
_hoverTimerId: ?TimeoutID;
|
||||
|
||||
constructor(props: EventsTreeProps) {
|
||||
super(props);
|
||||
|
||||
this.temporaryUnfoldedNodes = [];
|
||||
this.eventsHeightsCache = new EventHeightsCache(this);
|
||||
this.state = {
|
||||
...this._eventsToTreeData(props.events),
|
||||
draggedNode: null,
|
||||
isScrolledTop: true,
|
||||
isScrolledBottom: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -295,6 +344,10 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._hoverTimerId && clearTimeout(this._hoverTimerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called whenever an event height has changed
|
||||
*/
|
||||
@@ -359,8 +412,10 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
eventsList: gdEventsList,
|
||||
flatData: Array<gdBaseEvent> = [],
|
||||
depth: number = 0,
|
||||
parentDisabled: boolean = false
|
||||
parentDisabled: boolean = false,
|
||||
path: Array<number> = []
|
||||
) => {
|
||||
let eventIndexInFlattenedTree = 0;
|
||||
const treeData = mapFor<SortableTreeNode>(
|
||||
0,
|
||||
eventsList.getEventsCount(),
|
||||
@@ -369,6 +424,8 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
flatData.push(event);
|
||||
|
||||
const disabled = parentDisabled || event.isDisabled();
|
||||
const currentPath = path.concat(eventIndexInFlattenedTree);
|
||||
eventIndexInFlattenedTree += 1;
|
||||
|
||||
return {
|
||||
title: this._renderEvent,
|
||||
@@ -385,8 +442,10 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
// Hence it should not contain the folded events.
|
||||
!event.isFolded() ? flatData : [],
|
||||
depth + 1,
|
||||
disabled
|
||||
disabled,
|
||||
currentPath
|
||||
).treeData,
|
||||
nodePath: currentPath,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -405,6 +464,9 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
onAddEvent={(eventType: string) =>
|
||||
this.props.onAddNewEvent(eventType, this.props.events)
|
||||
}
|
||||
DnDComponent={this.DropTarget}
|
||||
draggedNode={this.state.draggedNode}
|
||||
rootEventsList={eventsList}
|
||||
/>
|
||||
),
|
||||
event: null,
|
||||
@@ -459,76 +521,31 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
].filter(Boolean);
|
||||
|
||||
return {
|
||||
// $FlowFixMe - We are confident treeData and extraNodes are both arrays of SortableTreeNode
|
||||
treeData: extraNodes.length ? treeData.concat(extraNodes) : treeData,
|
||||
flatData,
|
||||
};
|
||||
};
|
||||
|
||||
_onMoveNode = ({
|
||||
treeData,
|
||||
path,
|
||||
node,
|
||||
}: {
|
||||
treeData: any,
|
||||
path: Array<any>,
|
||||
node: SortableTreeNode,
|
||||
}) => {
|
||||
// Get the moved event and its list from the moved node.
|
||||
const { event, eventsList } = node;
|
||||
if (!event) return;
|
||||
|
||||
// Get the event list where the event should be moved to.
|
||||
const targetPath = path.slice(0, -1);
|
||||
const target = getNodeAtPath({
|
||||
getNodeKey,
|
||||
treeData: treeData,
|
||||
path: targetPath,
|
||||
});
|
||||
const targetNode = target.node;
|
||||
const targetEventsList =
|
||||
targetNode && targetNode.event
|
||||
? targetNode.event.getSubEvents()
|
||||
: this.props.events;
|
||||
const targetPosition =
|
||||
targetNode && targetNode.children ? targetNode.children.indexOf(node) : 0;
|
||||
|
||||
// Do the move
|
||||
// Note that moveEventToAnotherEventsList does not invalidate the
|
||||
// references to the event in memory - so things refering to this event like the
|
||||
// selection in EventsSheet remain valid. This might not be needed anymore
|
||||
// if events drag'n'drop is reworked to be similar to instructions drag'n'drop.
|
||||
eventsList.moveEventToAnotherEventsList(
|
||||
event,
|
||||
targetEventsList,
|
||||
targetPosition
|
||||
);
|
||||
|
||||
this.forceEventsUpdate();
|
||||
this.props.onEventMoved();
|
||||
};
|
||||
|
||||
_canDrag = ({ node }: { node: ?SortableTreeNode }) => {
|
||||
_canDrag = (node: ?SortableTreeNode) => {
|
||||
return !!node && !!node.event;
|
||||
};
|
||||
|
||||
_canDrop = ({ nextParent }: { nextParent: ?SortableTreeNode }) => {
|
||||
if (nextParent) {
|
||||
if (nextParent.event) {
|
||||
return nextParent.event.canHaveSubEvents();
|
||||
}
|
||||
}
|
||||
|
||||
// No "nextParent" means that we're trying to drop at the root
|
||||
// of the events tree.
|
||||
_canDrop = (hoveredNode: SortableTreeNode) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
_canNodeHaveChildren = (node: ?SortableTreeNode) => {
|
||||
if (node && node.event) {
|
||||
return node.event.canHaveSubEvents();
|
||||
_onDrop = (
|
||||
moveFunction: MoveFunctionArguments => void,
|
||||
currentNode: SortableTreeNode
|
||||
) => {
|
||||
if (this.state.draggedNode) {
|
||||
moveFunction({
|
||||
node: this.state.draggedNode,
|
||||
targetNode: currentNode,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
this.props.onEventMoved();
|
||||
};
|
||||
|
||||
_onVisibilityToggle = ({ node }: { node: SortableTreeNode }) => {
|
||||
@@ -557,54 +574,148 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
);
|
||||
};
|
||||
|
||||
_temporaryUnfoldNode = (isOverLazy: boolean, node: SortableTreeNode) => {
|
||||
const { event } = node;
|
||||
if (!event) return;
|
||||
|
||||
const isNodeTemporaryUnfolded = this.temporaryUnfoldedNodes.some(
|
||||
foldedNode => node.key === foldedNode.key
|
||||
);
|
||||
if (isOverLazy) {
|
||||
if (!this._hoverTimerId && !node.expanded) {
|
||||
if (!isNodeTemporaryUnfolded) {
|
||||
this._hoverTimerId = window.setTimeout(() => {
|
||||
// $FlowFixMe - Per the condition above, we are confident that node.event is not null.
|
||||
event.setFolded(false);
|
||||
this.temporaryUnfoldedNodes.push(node);
|
||||
this.forceEventsUpdate();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.clearTimeout(this._hoverTimerId);
|
||||
this._hoverTimerId = null;
|
||||
}
|
||||
};
|
||||
|
||||
_restoreFoldedNodes = () => {
|
||||
this.temporaryUnfoldedNodes.forEach(
|
||||
node => node.event && node.event.setFolded(true)
|
||||
);
|
||||
|
||||
this.temporaryUnfoldedNodes = [];
|
||||
this.forceEventsUpdate();
|
||||
};
|
||||
|
||||
_getRowHeight = ({ node }: { node: ?SortableTreeNode }) => {
|
||||
if (!node) return 0;
|
||||
if (!node.event) return node.fixedHeight || 0;
|
||||
|
||||
return this.eventsHeightsCache.getEventHeight(node.event);
|
||||
};
|
||||
|
||||
_onEndDrag = () => {
|
||||
this.setState({ draggedNode: null });
|
||||
this._restoreFoldedNodes();
|
||||
this.forceEventsUpdate();
|
||||
};
|
||||
|
||||
_renderEvent = ({ node }: { node: SortableTreeNode }) => {
|
||||
const { event, depth, disabled } = node;
|
||||
if (!event) return null;
|
||||
|
||||
const { DragSourceAndDropTarget, DropTarget } = this;
|
||||
const isDragged =
|
||||
!!this.state.draggedNode &&
|
||||
(isDescendant(this.state.draggedNode, node) ||
|
||||
node.key === this.state.draggedNode.key);
|
||||
return (
|
||||
<EventContainer
|
||||
project={this.props.project}
|
||||
scope={this.props.scope}
|
||||
globalObjectsContainer={this.props.globalObjectsContainer}
|
||||
objectsContainer={this.props.objectsContainer}
|
||||
event={event}
|
||||
key={event.ptr}
|
||||
eventsHeightsCache={this.eventsHeightsCache}
|
||||
selection={this.props.selection}
|
||||
leftIndentWidth={depth * getIndentWidth(this.props.windowWidth)}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
onEventClick={() =>
|
||||
this.props.onEventClick({
|
||||
eventsList: node.eventsList,
|
||||
event: event,
|
||||
indexInList: node.indexInList,
|
||||
})
|
||||
}
|
||||
onEventContextMenu={(x, y) =>
|
||||
this.props.onEventContextMenu(x, y, {
|
||||
eventsList: node.eventsList,
|
||||
event: event,
|
||||
indexInList: node.indexInList,
|
||||
})
|
||||
}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onAddInstructionContextMenu={this.props.onAddInstructionContextMenu}
|
||||
onOpenExternalEvents={this.props.onOpenExternalEvents}
|
||||
onOpenLayout={this.props.onOpenLayout}
|
||||
disabled={
|
||||
disabled /* Use node.disabled (not event.disabled) as it is true if a parent event is disabled*/
|
||||
}
|
||||
renderObjectThumbnail={this._renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
eventsSheetHeight={this.props.eventsSheetHeight}
|
||||
/>
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
this.setState({ draggedNode: node });
|
||||
return node;
|
||||
}}
|
||||
canDrag={() => this._canDrag(node)}
|
||||
canDrop={() => this._canDrop(node)}
|
||||
// Drop operations are handled by DropContainers.
|
||||
drop={() => {
|
||||
return;
|
||||
}}
|
||||
endDrag={this._onEndDrag}
|
||||
>
|
||||
{({ connectDragSource, connectDropTarget, isOverLazy }) => {
|
||||
this._temporaryUnfoldNode(isOverLazy, node);
|
||||
|
||||
const dropTarget = (
|
||||
<div
|
||||
style={{
|
||||
opacity: isDragged ? 0.5 : 1,
|
||||
...getEventContainerStyle(this.props.windowWidth),
|
||||
}}
|
||||
>
|
||||
<EventContainer
|
||||
project={this.props.project}
|
||||
scope={this.props.scope}
|
||||
globalObjectsContainer={this.props.globalObjectsContainer}
|
||||
objectsContainer={this.props.objectsContainer}
|
||||
event={event}
|
||||
key={event.ptr}
|
||||
eventsHeightsCache={this.eventsHeightsCache}
|
||||
selection={this.props.selection}
|
||||
leftIndentWidth={depth * getIndentWidth(this.props.windowWidth)}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
onEventClick={() =>
|
||||
this.props.onEventClick({
|
||||
eventsList: node.eventsList,
|
||||
event: event,
|
||||
indexInList: node.indexInList,
|
||||
})
|
||||
}
|
||||
onEventContextMenu={(x, y) =>
|
||||
this.props.onEventContextMenu(x, y, {
|
||||
eventsList: node.eventsList,
|
||||
event: event,
|
||||
indexInList: node.indexInList,
|
||||
})
|
||||
}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onAddInstructionContextMenu={
|
||||
this.props.onAddInstructionContextMenu
|
||||
}
|
||||
onOpenExternalEvents={this.props.onOpenExternalEvents}
|
||||
onOpenLayout={this.props.onOpenLayout}
|
||||
disabled={
|
||||
disabled /* Use node.disabled (not event.disabled) as it is true if a parent event is disabled*/
|
||||
}
|
||||
renderObjectThumbnail={this._renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
eventsSheetHeight={this.props.eventsSheetHeight}
|
||||
connectDragSource={connectDragSource}
|
||||
/>
|
||||
{this.state.draggedNode && (
|
||||
<DropContainer
|
||||
node={node}
|
||||
draggedNode={this.state.draggedNode}
|
||||
draggedNodeHeight={this._getRowHeight({
|
||||
node: this.state.draggedNode,
|
||||
})}
|
||||
DnDComponent={DropTarget}
|
||||
onDrop={this._onDrop}
|
||||
activateTargets={!isDragged && !!this.state.draggedNode}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return isDragged ? dropTarget : connectDropTarget(dropTarget);
|
||||
}}
|
||||
</DragSourceAndDropTarget>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -625,6 +736,14 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
);
|
||||
};
|
||||
|
||||
_scrollUp = () => {
|
||||
this._list && this._list.container.scrollBy({ top: -5 });
|
||||
};
|
||||
|
||||
_scrollDown = () => {
|
||||
this._list && this._list.container.scrollBy({ top: 5 });
|
||||
};
|
||||
|
||||
render() {
|
||||
// react-sortable-tree does the rendering by transforming treeData
|
||||
// into a flat array, the result being memoized. This hack forces
|
||||
@@ -641,20 +760,35 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
'--icon-size': `${Math.round(zoomLevel * 1.14)}px`,
|
||||
}}
|
||||
>
|
||||
{/* Disable for touchscreen because the dragged DOM node gets deleted, the */}
|
||||
{/* touch events are lost and the dnd does not drop anymore (hypothesis). */}
|
||||
{this.props.screenType !== 'touch' && (
|
||||
<>
|
||||
<AutoScroll
|
||||
DnDComponent={this.DropTarget}
|
||||
direction="top"
|
||||
activateTargets={
|
||||
!!this.state.draggedNode && !this.state.isScrolledTop
|
||||
}
|
||||
onHover={this._scrollUp}
|
||||
/>
|
||||
<AutoScroll
|
||||
DnDComponent={this.DropTarget}
|
||||
direction="bottom"
|
||||
activateTargets={
|
||||
!!this.state.draggedNode && !this.state.isScrolledBottom
|
||||
}
|
||||
onHover={this._scrollDown}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SortableTree
|
||||
treeData={treeData}
|
||||
scaffoldBlockPxWidth={getIndentWidth(this.props.windowWidth)}
|
||||
onChange={noop}
|
||||
onVisibilityToggle={this._onVisibilityToggle}
|
||||
onMoveNode={this._onMoveNode}
|
||||
canDrag={this._canDrag}
|
||||
canDrop={this._canDrop}
|
||||
canNodeHaveChildren={this._canNodeHaveChildren}
|
||||
rowHeight={({ node }: { node: SortableTreeNode }) => {
|
||||
if (!node.event) return node.fixedHeight || 0;
|
||||
|
||||
return this.eventsHeightsCache.getEventHeight(node.event);
|
||||
}}
|
||||
canDrag={false}
|
||||
rowHeight={this._getRowHeight}
|
||||
searchMethod={this._isNodeHighlighted}
|
||||
searchQuery={this.props.searchResults}
|
||||
searchFocusOffset={this.props.searchFocusOffset}
|
||||
@@ -663,8 +797,19 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
}
|
||||
reactVirtualizedListProps={{
|
||||
ref: list => (this._list = list),
|
||||
onScroll: this.props.onScroll,
|
||||
onScroll: event => {
|
||||
this.props.onScroll && this.props.onScroll();
|
||||
this.setState({
|
||||
isScrolledTop: event.scrollTop === 0,
|
||||
isScrolledBottom:
|
||||
event.clientHeight + event.scrollTop >= event.scrollHeight,
|
||||
});
|
||||
},
|
||||
}}
|
||||
// Disable slideRegionSize on touchscreen because of a bug that makes scrolling
|
||||
// uncontrollable on touchscreens. Ternary operator does not update slideRegionSize
|
||||
// well.
|
||||
slideRegionSize={-10}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@@ -9,11 +9,12 @@
|
||||
/**
|
||||
* Draggable handle on the left of an event
|
||||
*/
|
||||
.gd-events-sheet .rst__moveHandle {
|
||||
.gd-events-sheet .move-handle {
|
||||
width: 10px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
margin: 0.5px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
@@ -21,7 +22,7 @@
|
||||
* Slightly reduce the size of the handle on small screens
|
||||
* and phones (where drag'n'drop is not supported anyway).
|
||||
*/
|
||||
.gd-events-sheet .rst__moveHandle {
|
||||
.gd-events-sheet .move-handle {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ const styles = {
|
||||
columnDropIndicator: {
|
||||
borderRight: '1px solid',
|
||||
borderLeft: '1px solid',
|
||||
width: 7,
|
||||
width: 8,
|
||||
marginLeft: '-1px',
|
||||
height: '100%',
|
||||
boxSizing: 'border-box',
|
||||
@@ -19,8 +19,8 @@ export function ColumnDropIndicator() {
|
||||
<div
|
||||
style={{
|
||||
...styles.columnDropIndicator,
|
||||
backgroundColor: gdevelopTheme.closableTabs.selectedBackgroundColor,
|
||||
borderColor: gdevelopTheme.closableTabs.backgroundColor,
|
||||
backgroundColor: gdevelopTheme.dropIndicator.canDrop,
|
||||
borderColor: gdevelopTheme.dropIndicator.border,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@@ -16,12 +16,14 @@ type Props<DraggedItemType> = {|
|
||||
connectDragSource: ConnectDragSource,
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
isOverLazy: boolean,
|
||||
canDrop: boolean,
|
||||
}) => ?React.Node,
|
||||
beginDrag: () => DraggedItemType,
|
||||
canDrag?: (item: DraggedItemType) => boolean,
|
||||
canDrop: (item: DraggedItemType) => boolean,
|
||||
drop: () => void,
|
||||
endDrag?: () => void,
|
||||
|};
|
||||
|
||||
type DragSourceProps = {|
|
||||
@@ -31,6 +33,7 @@ type DragSourceProps = {|
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
isOverLazy: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
@@ -53,6 +56,9 @@ export const makeDragSourceAndDropTarget = <DraggedItemType>(
|
||||
beginDrag(props: InnerDragSourceAndDropTargetProps<DraggedItemType>) {
|
||||
return props.beginDrag();
|
||||
},
|
||||
endDrag(props: Props<DraggedItemType>, monitor: DragSourceMonitor) {
|
||||
if (props.endDrag) props.endDrag();
|
||||
},
|
||||
};
|
||||
|
||||
function sourceCollect(
|
||||
@@ -84,6 +90,7 @@ export const makeDragSourceAndDropTarget = <DraggedItemType>(
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
isOverLazy: monitor.isOver({ shallow: false }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
@@ -94,11 +101,19 @@ export const makeDragSourceAndDropTarget = <DraggedItemType>(
|
||||
sourceCollect
|
||||
)(
|
||||
DropTarget(reactDndType, targetSpec, targetCollect)(
|
||||
({ children, connectDragSource, connectDropTarget, isOver, canDrop }) => {
|
||||
({
|
||||
children,
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
isOverLazy,
|
||||
canDrop,
|
||||
}) => {
|
||||
return children({
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
isOverLazy,
|
||||
canDrop,
|
||||
});
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ type Props<DraggedItemType> = {|
|
||||
children: ({
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
isOverLazy: boolean,
|
||||
canDrop: boolean,
|
||||
}) => ?React.Node,
|
||||
canDrop: (item: DraggedItemType) => boolean,
|
||||
@@ -18,15 +19,20 @@ type Props<DraggedItemType> = {|
|
||||
drop: (monitor: DropTargetMonitor) => void,
|
||||
|};
|
||||
|
||||
export type DropTargetComponent<DraggedItemType> = (
|
||||
Props<DraggedItemType>
|
||||
) => React.Node;
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
isOverLazy: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
export const makeDropTarget = <DraggedItemType>(
|
||||
reactDndType: string
|
||||
): ((Props<DraggedItemType>) => React.Node) => {
|
||||
): DropTargetComponent<DraggedItemType> => {
|
||||
const targetSpec = {
|
||||
canDrop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
const item = monitor.getItem();
|
||||
@@ -50,15 +56,17 @@ export const makeDropTarget = <DraggedItemType>(
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
isOverLazy: monitor.isOver({ shallow: false }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
const InnerDropTarget = DropTarget(reactDndType, targetSpec, targetCollect)(
|
||||
({ children, connectDropTarget, isOver, canDrop }) => {
|
||||
({ children, connectDropTarget, isOver, isOverLazy, canDrop }) => {
|
||||
return children({
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
isOverLazy,
|
||||
canDrop,
|
||||
});
|
||||
}
|
||||
|
@@ -20,8 +20,8 @@ export default function DropIndicator({ canDrop }: {| canDrop: boolean |}) {
|
||||
style={{
|
||||
...styles.dropIndicator,
|
||||
borderColor: canDrop
|
||||
? gdevelopTheme.listItem.selectedBackgroundColor
|
||||
: gdevelopTheme.listItem.selectedErrorBackgroundColor,
|
||||
? gdevelopTheme.dropIndicator.canDrop
|
||||
: gdevelopTheme.dropIndicator.cannotDrop,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@@ -219,6 +219,11 @@ export function createGdevelopTheme(
|
||||
backgroundColor: styles['ThemeTextHighlightedBackgroundColor'],
|
||||
},
|
||||
},
|
||||
dropIndicator: {
|
||||
canDrop: styles['ThemeDropIndicatorCanDropColor'],
|
||||
cannotDrop: styles['ThemeDropIndicatorCannotDropColor'],
|
||||
border: styles['ThemeDropIndicatorBorderColor'],
|
||||
},
|
||||
closableTabs: {
|
||||
fontFamily: styles['GdevelopFontFamily'],
|
||||
containerBackgroundColor: styles['ThemeSurfaceWindowBackgroundColor'],
|
||||
|
@@ -165,6 +165,23 @@
|
||||
"frame-border-color": {
|
||||
"value": "#dddddd"
|
||||
}
|
||||
},
|
||||
"drop-indicator": {
|
||||
"can-drop": {
|
||||
"color": {
|
||||
"value": "{gdevelop.color.dark-blue.value}"
|
||||
}
|
||||
},
|
||||
"cannot-drop": {
|
||||
"color": {
|
||||
"value": "#ff5c5c"
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"color": {
|
||||
"value": "{theme.surface.canvas.background-color.value}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -166,6 +166,23 @@
|
||||
"frame-border-color": {
|
||||
"value": "#000000"
|
||||
}
|
||||
},
|
||||
"drop-indicator": {
|
||||
"can-drop": {
|
||||
"color": {
|
||||
"value": "{gdevelop.color.light-blue.value}"
|
||||
}
|
||||
},
|
||||
"cannot-drop": {
|
||||
"color": {
|
||||
"value": "#ff5c5c"
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"color": {
|
||||
"value": "{theme.surface.canvas.background-color.value}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
.gd-events-sheet .events-tree {
|
||||
/* Use the fonts provided by the operating system(s) as possible. */
|
||||
/* If you update this font list, be sure to do it in all the other places using fonts in the codebase. */
|
||||
font-family: var(--gdevelop-font-family) !important;
|
||||
background: var(--event-sheet-event-tree-background-color);
|
||||
/* Use the fonts provided by the operating system(s) as possible. */
|
||||
/* If you update this font list, be sure to do it in all the other places using fonts in the codebase. */
|
||||
font-family: var(--gdevelop-font-family) !important;
|
||||
background: var(--event-sheet-event-tree-background-color);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,18 +15,18 @@
|
||||
* of horizontal scrollbar.
|
||||
*/
|
||||
.gd-events-sheet .rst__virtualScrollOverride {
|
||||
overflow-x: hidden !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draggable handle on the left of an event
|
||||
*/
|
||||
.gd-events-sheet .rst__moveHandle {
|
||||
background: var(--event-sheet-rst-move-handle-background-color);
|
||||
.gd-events-sheet .move-handle {
|
||||
background: var(--event-sheet-rst-move-handle-background-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__moveHandle:hover {
|
||||
background-color: var(--event-sheet-rst-move-handle-hover-background-color);
|
||||
.gd-events-sheet .move-handle:hover {
|
||||
background-color: var(--event-sheet-rst-move-handle-hover-background-color);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,89 +36,89 @@
|
||||
.gd-events-sheet .rst__lineFullVertical::after,
|
||||
.gd-events-sheet .rst__lineHalfVerticalTop::after,
|
||||
.gd-events-sheet .rst__lineHalfVerticalBottom::after {
|
||||
background-color: var(--event-sheet-rst-line-background-color);
|
||||
background-color: var(--event-sheet-rst-line-background-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Background and default text color of content of an event
|
||||
*/
|
||||
.gd-events-sheet .rst__rowContents {
|
||||
background-color: var(--event-sheet-rst-row-contents-background-color);
|
||||
color: var(--event-sheet-rst-row-contents-color);
|
||||
background-color: var(--event-sheet-rst-row-contents-background-color);
|
||||
color: var(--event-sheet-rst-row-contents-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selectable area (instructions)
|
||||
*/
|
||||
.gd-events-sheet .selectable:hover {
|
||||
background-color: var(--event-sheet-selectable-background-color);
|
||||
background-color: var(--event-sheet-selectable-background-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .selectable.selected {
|
||||
background-color: var(--event-sheet-selectable-background-color);
|
||||
border: var(--event-sheet-selectable-selected-border) !important;
|
||||
background-color: var(--event-sheet-selectable-background-color);
|
||||
border: var(--event-sheet-selectable-selected-border) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Large selectable area (events)
|
||||
*/
|
||||
.gd-events-sheet .large-selectable {
|
||||
border: var(--event-sheet-selectable-border) !important;
|
||||
border: var(--event-sheet-selectable-border) !important;
|
||||
}
|
||||
|
||||
.gd-events-sheet .large-selectable.large-selected {
|
||||
border: var(--event-sheet-selectable-selected-border) !important;
|
||||
border: var(--event-sheet-selectable-selected-border) !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditions and actions containers
|
||||
*/
|
||||
.gd-events-sheet .conditions-container {
|
||||
background: var(--event-sheet-conditions-background-color);
|
||||
border: var(--event-sheet-conditions-border);
|
||||
color: var(--event-sheet-conditions-color);
|
||||
border-right-color: var(--event-sheet-conditions-border-right-color);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
/* Prevent container to growing outside its parent - and still don't have a ridiculously small size */
|
||||
min-width: 100px;
|
||||
background: var(--event-sheet-conditions-background-color);
|
||||
border: var(--event-sheet-conditions-border);
|
||||
color: var(--event-sheet-conditions-color);
|
||||
border-right-color: var(--event-sheet-conditions-border-right-color);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
/* Prevent container to growing outside its parent - and still don't have a ridiculously small size */
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.gd-events-sheet .conditions-container.small-width-container {
|
||||
border-right-color: var(--event-sheet-conditions-border-color);
|
||||
border-bottom-color: var(--event-sheet-conditions-border-color);
|
||||
border-right-color: var(--event-sheet-conditions-border-color);
|
||||
border-bottom-color: var(--event-sheet-conditions-border-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .actions-container {
|
||||
background: var(--event-sheet-actions-background-color);
|
||||
color: var(--event-sheet-actions-color);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
/* Prevent container to growing outside its parent. We don't have a way to prevent an infinitely
|
||||
background: var(--event-sheet-actions-background-color);
|
||||
color: var(--event-sheet-actions-color);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
/* Prevent container to growing outside its parent. We don't have a way to prevent an infinitely
|
||||
* small action column, but responsive design should avoid the problem on small screens. */
|
||||
min-width: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gd-events-sheet .actions-container.small-width-container {
|
||||
padding-left: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.gd-events-sheet .sub-instructions-container {
|
||||
margin-left: 9px;
|
||||
margin-top: 1px;
|
||||
border-right: none;
|
||||
border-left: var(--event-sheet-sub-instructions-border);
|
||||
margin-left: 9px;
|
||||
margin-top: 1px;
|
||||
border-right: none;
|
||||
border-left: var(--event-sheet-sub-instructions-border);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions parameters color scheme
|
||||
*/
|
||||
.gd-events-sheet .instruction-parameter {
|
||||
color: var(--event-sheet-instruction-parameter-base-color);
|
||||
color: var(--event-sheet-instruction-parameter-base-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.expression {
|
||||
color: var(--event-sheet-instruction-parameter-expression-color);
|
||||
color: var(--event-sheet-instruction-parameter-expression-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.object,
|
||||
@@ -126,112 +126,112 @@
|
||||
.gd-events-sheet .instruction-parameter.objectList,
|
||||
.gd-events-sheet .instruction-parameter.objectListOrEmptyIfJustDeclared,
|
||||
.gd-events-sheet .instruction-parameter.objectListOrEmptyWithoutPicking {
|
||||
color:var(--event-sheet-instruction-parameter-object-color);
|
||||
font-weight: bold;
|
||||
color:var(--event-sheet-instruction-parameter-object-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.behavior {
|
||||
color: var(--event-sheet-instruction-parameter-behavior-color);
|
||||
color: var(--event-sheet-instruction-parameter-behavior-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.operator {
|
||||
color: var(--event-sheet-instruction-parameter-operator-color);
|
||||
color: var(--event-sheet-instruction-parameter-operator-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.objectvar {
|
||||
color: var(--event-sheet-instruction-parameter-var-color);
|
||||
color: var(--event-sheet-instruction-parameter-var-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.scenevar {
|
||||
color: var(--event-sheet-instruction-parameter-var-color);
|
||||
color: var(--event-sheet-instruction-parameter-var-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter.globalvar {
|
||||
color: var(--event-sheet-instruction-parameter-var-color);
|
||||
color: var(--event-sheet-instruction-parameter-var-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter .instruction-invalid-parameter {
|
||||
color: var(--event-sheet-instruction-parameter-error-color);
|
||||
text-decoration: var(--event-sheet-instruction-parameter-error-color) underline wavy;
|
||||
color: var(--event-sheet-instruction-parameter-error-color);
|
||||
text-decoration: var(--event-sheet-instruction-parameter-error-color) underline wavy;
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter .instruction-warning-parameter {
|
||||
color: var(--event-sheet-instruction-parameter-warning-color);
|
||||
text-decoration: var(--event-sheet-instruction-parameter-warning-color) underline wavy;
|
||||
color: var(--event-sheet-instruction-parameter-warning-color);
|
||||
text-decoration: var(--event-sheet-instruction-parameter-warning-color) underline wavy;
|
||||
}
|
||||
|
||||
.gd-events-sheet .instruction-parameter .instruction-missing-parameter {
|
||||
display: inline-flex;
|
||||
background-color: var(--event-sheet-instruction-parameter-error-background-color);
|
||||
border-bottom: 1px dashed var(--event-sheet-instruction-parameter-error-color);
|
||||
min-width: 25px;
|
||||
min-height: 14px;
|
||||
display: inline-flex;
|
||||
background-color: var(--event-sheet-instruction-parameter-error-background-color);
|
||||
border-bottom: 1px dashed var(--event-sheet-instruction-parameter-error-color);
|
||||
min-width: 25px;
|
||||
min-height: 14px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search results highlight
|
||||
*/
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch .rst__moveHandle {
|
||||
background: var(--event-sheet-events-highlighted-color);
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch .move-handle {
|
||||
background: var(--event-sheet-events-highlighted-color);
|
||||
}
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch .rst__moveHandle:hover {
|
||||
filter: brightness(120%);
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch .move-handle:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch .conditions-container {
|
||||
outline: none;
|
||||
background-color: var(--event-sheet-events-highlighted-background-color);
|
||||
outline: none;
|
||||
background-color: var(--event-sheet-events-highlighted-background-color);
|
||||
}
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchMatch .actions-container {
|
||||
outline: none;
|
||||
background-color: var(--event-sheet-events-highlighted-background-color);
|
||||
outline: none;
|
||||
background-color: var(--event-sheet-events-highlighted-background-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchFocus .rst__moveHandle {
|
||||
width: 15px;
|
||||
.gd-events-sheet .with-search-results .rst__rowSearchFocus .move-handle {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag'n'drop indicator (events)
|
||||
* Drag'n'drop indicator (events - instructions)
|
||||
*/
|
||||
.gd-events-sheet .drop-indicator {
|
||||
border-top: 2px solid var(--event-sheet-drop-indicator-can-drop-border-top-color);
|
||||
height: 0;
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 2px solid var(--event-sheet-drop-indicator-can-drop-border-top-color);
|
||||
height: 0;
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gd-events-sheet .cant-drop-indicator {
|
||||
border-top: 2px solid var(--event-sheet-drop-indicator-can-drop-border-top-color);
|
||||
height: 0;
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 2px solid var(--event-sheet-drop-indicator-can-drop-border-top-color);
|
||||
height: 0;
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skinning the "Link" event
|
||||
*/
|
||||
.gd-events-sheet .link-container {
|
||||
background: var(--event-sheet-link-container-background-color);
|
||||
background: var(--event-sheet-link-container-background-color);
|
||||
}
|
||||
.gd-events-sheet-dark-theme .link-container span {
|
||||
color: var(--event-sheet-link-container-color);
|
||||
color: var(--event-sheet-link-container-color);
|
||||
}
|
||||
|
||||
.gd-events-sheet .link-container .selectable {
|
||||
color: var(--event-sheet-link-selectable-link-color);
|
||||
font-weight: bold;
|
||||
color: var(--event-sheet-link-selectable-link-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Theming icons */
|
||||
.gd-events-sheet .icon {
|
||||
padding-right: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding-right: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
@@ -165,6 +165,23 @@
|
||||
"frame-border-color": {
|
||||
"value": "#ECEFF4"
|
||||
}
|
||||
},
|
||||
"drop-indicator": {
|
||||
"can-drop": {
|
||||
"color": {
|
||||
"value": "{theme.primary.color.value}"
|
||||
}
|
||||
},
|
||||
"cannot-drop": {
|
||||
"color": {
|
||||
"value": "#BF616A"
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"color": {
|
||||
"value": "{theme.surface.canvas.background-color.value}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -175,6 +175,23 @@
|
||||
"frame-border-color": {
|
||||
"value": "#D6DEEC"
|
||||
}
|
||||
},
|
||||
"drop-indicator": {
|
||||
"can-drop": {
|
||||
"color": {
|
||||
"value": "#4C5361"
|
||||
}
|
||||
},
|
||||
"cannot-drop": {
|
||||
"color": {
|
||||
"value": "#E06C75"
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"color": {
|
||||
"value": "#21252B"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
@@ -165,6 +165,23 @@
|
||||
"frame-border-color": {
|
||||
"value": "#dcdcdc"
|
||||
}
|
||||
},
|
||||
"drop-indicator": {
|
||||
"can-drop": {
|
||||
"color": {
|
||||
"value": "#047AA6"
|
||||
}
|
||||
},
|
||||
"cannot-drop": {
|
||||
"color": {
|
||||
"value": "#BF616A"
|
||||
}
|
||||
},
|
||||
"border": {
|
||||
"color": {
|
||||
"value": "{theme.surface.canvas.background-color.value}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
|
Reference in New Issue
Block a user