mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Make newIDE context menus dynamic (greyed-out items if not applicable, checkbox for grid)
This commit is contained in:
17
newIDE/app/flow-typed/libGD.js
vendored
17
newIDE/app/flow-typed/libGD.js
vendored
@@ -2,11 +2,16 @@
|
||||
|
||||
//TODO: These types could be generated from GDevelop.js instead of being
|
||||
//manually written here.
|
||||
declare type gdProject = Object;
|
||||
declare type gdLayout = Object;
|
||||
declare type gdExternalLayout = Object;
|
||||
declare type gdExternalEvents = Object;
|
||||
declare type gdSerializerElement = Object;
|
||||
type EmscriptenObject = {
|
||||
ptr: Number
|
||||
};
|
||||
|
||||
declare type gdProject = EmscriptenObject;
|
||||
declare type gdLayout = EmscriptenObject;
|
||||
declare type gdExternalLayout = EmscriptenObject;
|
||||
declare type gdExternalEvents = EmscriptenObject;
|
||||
declare type gdSerializerElement = EmscriptenObject;
|
||||
declare type gdInitialInstance = EmscriptenObject;
|
||||
|
||||
//Represents all objects that have serializeTo and unserializeFrom methods.
|
||||
declare type gdSerializable = Object;
|
||||
declare type gdSerializable = EmscriptenObject;
|
||||
|
@@ -70,12 +70,13 @@ export class Toolbar extends PureComponent {
|
||||
tooltip={t('Choose and add an event')}
|
||||
/>
|
||||
}
|
||||
menuTemplate={this.allEventsMetadata.map(metadata => {
|
||||
return {
|
||||
label: metadata.fullName,
|
||||
click: () => this.props.onAddEvent(metadata.type),
|
||||
};
|
||||
})}
|
||||
buildMenuTemplate={() =>
|
||||
this.allEventsMetadata.map(metadata => {
|
||||
return {
|
||||
label: metadata.fullName,
|
||||
click: () => this.props.onAddEvent(metadata.type),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<ToolbarSeparator />
|
||||
<ToolbarIcon
|
||||
|
@@ -504,7 +504,7 @@ export default class EventsSheet extends Component {
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={eventContextMenu => (this.eventContextMenu = eventContextMenu)}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
@@ -518,6 +518,7 @@ export default class EventsSheet extends Component {
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteEvents(),
|
||||
enabled: Clipboard.has(CLIPBOARD_KIND),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
@@ -530,11 +531,13 @@ export default class EventsSheet extends Component {
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
]}
|
||||
@@ -542,7 +545,7 @@ export default class EventsSheet extends Component {
|
||||
<ContextMenu
|
||||
ref={instructionContextMenu =>
|
||||
(this.instructionContextMenu = instructionContextMenu)}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
@@ -556,6 +559,7 @@ export default class EventsSheet extends Component {
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteInstructions(),
|
||||
enabled: Clipboard.has(CLIPBOARD_KIND),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
@@ -568,11 +572,13 @@ export default class EventsSheet extends Component {
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
]}
|
||||
@@ -580,21 +586,24 @@ export default class EventsSheet extends Component {
|
||||
<ContextMenu
|
||||
ref={instructionsListContextMenu =>
|
||||
(this.instructionsListContextMenu = instructionsListContextMenu)}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteInstructions(),
|
||||
enabled: Clipboard.has(CLIPBOARD_KIND),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
]}
|
||||
|
@@ -57,7 +57,7 @@ export class MainFrameToolbar extends Component {
|
||||
iconButtonElement={
|
||||
<ToolbarIcon src="res/ribbon_default/bug32.png" />
|
||||
}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Request update from external editor',
|
||||
click: () => this.props.requestUpdate(),
|
||||
|
@@ -266,7 +266,7 @@ class AnimationsListContainer extends Component {
|
||||
<ContextMenu
|
||||
ref={spriteContextMenu =>
|
||||
(this.spriteContextMenu = spriteContextMenu)}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this.deleteSelection(),
|
||||
|
@@ -35,7 +35,7 @@ export default class GroupRow extends React.Component {
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={()=> [
|
||||
{
|
||||
label: 'Edit group',
|
||||
click: () => this.props.onEdit(group),
|
||||
|
1
newIDE/app/src/ObjectsList/ClipboardKind.js
Normal file
1
newIDE/app/src/ObjectsList/ClipboardKind.js
Normal file
@@ -0,0 +1 @@
|
||||
export const CLIPBOARD_KIND = 'Object';
|
@@ -5,6 +5,8 @@ import ListIcon from '../UI/ListIcon';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
|
||||
import Clipboard from '../Utils/Clipboard';
|
||||
import { CLIPBOARD_KIND } from './ClipboardKind';
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
@@ -35,7 +37,7 @@ export default class ObjectRow extends React.Component {
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Edit object',
|
||||
enabled: !!this.props.onEdit,
|
||||
@@ -73,6 +75,7 @@ export default class ObjectRow extends React.Component {
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
enabled: Clipboard.has(CLIPBOARD_KIND),
|
||||
click: () => this.props.onPaste(),
|
||||
},
|
||||
]}
|
||||
|
@@ -21,8 +21,7 @@ import type {
|
||||
ObjectWithContextList,
|
||||
ObjectWithContext,
|
||||
} from '../ObjectsList/EnumerateObjects';
|
||||
|
||||
const CLIPBOARD_KIND = 'Object';
|
||||
import { CLIPBOARD_KIND } from './ClipboardKind';
|
||||
|
||||
const listItemHeight = 48;
|
||||
const styles = {
|
||||
|
@@ -91,7 +91,7 @@ class Item extends Component {
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Edit',
|
||||
click: () => this.props.onEdit(),
|
||||
@@ -115,6 +115,7 @@ class Item extends Component {
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
enabled: this.props.canPaste(),
|
||||
click: () => this.props.onPaste(),
|
||||
},
|
||||
]}
|
||||
@@ -376,6 +377,7 @@ export default class ProjectManager extends React.Component {
|
||||
onCopy={() => this._copyLayout(layout)}
|
||||
onCut={() => this._cutLayout(layout)}
|
||||
onPaste={() => this._pasteLayout(i)}
|
||||
canPaste={() => Clipboard.has(LAYOUT_CLIPBOARD_KIND)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -421,6 +423,7 @@ export default class ProjectManager extends React.Component {
|
||||
onCopy={() => this._copyExternalEvents(externalEvents)}
|
||||
onCut={() => this._cutExternalEvents(externalEvents)}
|
||||
onPaste={() => this._pasteExternalEvents(i)}
|
||||
canPaste={() => Clipboard.has(EXTERNAL_EVENTS_CLIPBOARD_KIND)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
@@ -466,6 +469,7 @@ export default class ProjectManager extends React.Component {
|
||||
onCopy={() => this._copyExternalLayout(externalLayout)}
|
||||
onCut={() => this._cutExternalLayout(externalLayout)}
|
||||
onPaste={() => this._pasteExternalLayout(i)}
|
||||
canPaste={() => Clipboard.has(EXTERNAL_LAYOUT_CLIPBOARD_KIND)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
@@ -1,15 +1,18 @@
|
||||
// @flow
|
||||
const gd = global.gd;
|
||||
|
||||
export default class InstancesSelection {
|
||||
constructor() {
|
||||
this.selection = [];
|
||||
selection: Array<gdInitialInstance> = [];
|
||||
|
||||
hasSelectedInstances() {
|
||||
return !!this.getSelectedInstances().length;
|
||||
}
|
||||
|
||||
getSelectedInstances() {
|
||||
return this.selection;
|
||||
}
|
||||
|
||||
isInstanceSelected(instance) {
|
||||
isInstanceSelected(instance: gdInitialInstance) {
|
||||
for (var i = 0; i < this.selection.length; i++) {
|
||||
if (gd.compare(this.selection[i], instance)) return true;
|
||||
}
|
||||
@@ -21,7 +24,7 @@ export default class InstancesSelection {
|
||||
this.selection.length = 0;
|
||||
}
|
||||
|
||||
selectInstance(instance, multiselect) {
|
||||
selectInstance(instance: gdInitialInstance, multiselect: boolean) {
|
||||
if (this.isInstanceSelected(instance)) {
|
||||
if (multiselect) this.unselectInstance(instance);
|
||||
|
||||
@@ -32,13 +35,13 @@ export default class InstancesSelection {
|
||||
this.selection.push(instance);
|
||||
}
|
||||
|
||||
selectInstances(instances, multiselect) {
|
||||
selectInstances(instances: [gdInitialInstance], multiselect: boolean) {
|
||||
if (!multiselect) this.clearSelection();
|
||||
|
||||
instances.forEach(instance => this.selectInstance(instance, true));
|
||||
}
|
||||
|
||||
unselectInstance(instance) {
|
||||
unselectInstance(instance: gdInitialInstance) {
|
||||
if (this.isInstanceSelected(instance)) {
|
||||
var i = this.selection.length - 1;
|
||||
while (i >= -1 && this.selection[i].ptr !== instance.ptr) {
|
||||
|
@@ -22,7 +22,6 @@ export default class SetupGridDialog extends Component {
|
||||
const actions = [
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this.props.onCancel}
|
||||
/>,
|
||||
<FlatButton
|
||||
|
@@ -83,9 +83,11 @@ export class Toolbar extends PureComponent {
|
||||
tooltip={t('Toggle/edit grid')}
|
||||
/>
|
||||
}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Toggle grid',
|
||||
type: 'checkbox',
|
||||
label: 'Show grid',
|
||||
checked: this.props.isGridShown(),
|
||||
click: () => this.props.toggleGrid(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
@@ -102,7 +104,7 @@ export class Toolbar extends PureComponent {
|
||||
tooltip={t('Change editor zoom')}
|
||||
/>
|
||||
}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Zoom in',
|
||||
click: this.props.zoomIn,
|
||||
|
@@ -40,6 +40,8 @@ import {
|
||||
} from '../../Utils/History';
|
||||
const gd = global.gd;
|
||||
|
||||
const INSTANCES_CLIPBOARD_KIND = 'Instances';
|
||||
|
||||
const FullSizeInstancesEditor = passFullSize(addScrollbars(InstancesEditor), {
|
||||
useFlex: true,
|
||||
});
|
||||
@@ -107,6 +109,7 @@ export default class InstancesFullEditor extends Component {
|
||||
toggleLayersList={this.toggleLayersList}
|
||||
toggleWindowMask={this.toggleWindowMask}
|
||||
toggleGrid={this.toggleGrid}
|
||||
isGridShown={() => !!this.state.uiSettings.grid}
|
||||
openSetupGrid={this.openSetupGrid}
|
||||
setZoomFactor={this.setZoomFactor}
|
||||
canUndo={canUndo(this.state.history)}
|
||||
@@ -436,7 +439,7 @@ export default class InstancesFullEditor extends Component {
|
||||
const position = useLastCursorPosition
|
||||
? this.editor.getLastCursorPosition()
|
||||
: this.editor.getLastContextMenuPosition();
|
||||
Clipboard.set('instances', {
|
||||
Clipboard.set(INSTANCES_CLIPBOARD_KIND, {
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
instances: serializedSelection,
|
||||
@@ -449,7 +452,7 @@ export default class InstancesFullEditor extends Component {
|
||||
};
|
||||
|
||||
paste = ({ useLastCursorPosition } = {}) => {
|
||||
const clipboardContent = Clipboard.get('instances');
|
||||
const clipboardContent = Clipboard.get(INSTANCES_CLIPBOARD_KIND);
|
||||
if (!clipboardContent) return;
|
||||
|
||||
const position = useLastCursorPosition
|
||||
@@ -682,7 +685,7 @@ export default class InstancesFullEditor extends Component {
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={contextMenu => (this.contextMenu = contextMenu)}
|
||||
menuTemplate={[
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Scene properties',
|
||||
click: () => this.openSceneProperties(true),
|
||||
@@ -691,27 +694,32 @@ export default class InstancesFullEditor extends Component {
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
enabled: this.instancesSelection.hasSelectedInstances(),
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this.cutSelection(),
|
||||
enabled: this.instancesSelection.hasSelectedInstances(),
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.paste(),
|
||||
enabled: Clipboard.has(INSTANCES_CLIPBOARD_KIND),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
]}
|
||||
|
@@ -56,7 +56,9 @@ class MaterialUIContextMenu extends React.Component {
|
||||
{...this.menuImplementation.getMenuProps()}
|
||||
>
|
||||
<Menu desktop width={256}>
|
||||
{this.menuImplementation.buildFromTemplate(this.props.menuTemplate)}
|
||||
{this.menuImplementation.buildFromTemplate(
|
||||
this.props.buildMenuTemplate()
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
</div>
|
||||
@@ -68,16 +70,10 @@ class ElectronContextMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.menuImplementation = new ElectronMenuImplementation();
|
||||
this.menuImplementation.buildFromTemplate(props.menuTemplate);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.menuTemplate !== nextProps.menuTemplate) {
|
||||
this.menuImplementation.buildFromTemplate(nextProps.menuTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
open = (x, y) => {
|
||||
this.menuImplementation.buildFromTemplate(this.props.buildMenuTemplate());
|
||||
this.menuImplementation.showMenu({
|
||||
left: x || 0,
|
||||
top: y || 0,
|
||||
|
@@ -1,6 +1,9 @@
|
||||
import optionalRequire from '../../Utils/OptionalRequire.js';
|
||||
const electron = optionalRequire('electron');
|
||||
|
||||
/**
|
||||
* Wraps an Electron Menu
|
||||
*/
|
||||
export default class ElectronMenuImplementation {
|
||||
buildFromTemplate(template) {
|
||||
this.menuTemplate = template;
|
||||
|
@@ -17,32 +17,20 @@ export default class GDIconMenu extends Component {
|
||||
: new MaterialUIMenuImplementation({ onClose: () => {} });
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
children: this.menuImplementation.buildFromTemplate(
|
||||
this.props.menuTemplate
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.menuTemplate !== nextProps.menuTemplate) {
|
||||
this.setState({
|
||||
children: this.menuImplementation.buildFromTemplate(
|
||||
nextProps.menuTemplate
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
open = event => {
|
||||
if (!this.iconMenu) return;
|
||||
|
||||
const node = ReactDOM.findDOMNode(this.iconMenu);
|
||||
if (!node) return;
|
||||
|
||||
this.setState({
|
||||
children: this.menuImplementation.buildFromTemplate(
|
||||
this.props.buildMenuTemplate()
|
||||
),
|
||||
});
|
||||
this.menuImplementation.showMenu(node.getBoundingClientRect());
|
||||
this.iconMenu.open('unkown', event);
|
||||
|
||||
this.iconMenu.open('unknown', event);
|
||||
};
|
||||
|
||||
_onTouchTap = event => {
|
||||
@@ -53,11 +41,16 @@ export default class GDIconMenu extends Component {
|
||||
const node = ReactDOM.findDOMNode(this.iconMenu);
|
||||
if (!node) return;
|
||||
|
||||
this.setState({
|
||||
children: this.menuImplementation.buildFromTemplate(
|
||||
this.props.buildMenuTemplate()
|
||||
),
|
||||
});
|
||||
this.menuImplementation.showMenu(node.getBoundingClientRect());
|
||||
};
|
||||
|
||||
render() {
|
||||
const { menuTemplate, ...iconMenuProps } = this.props; //eslint-disable-line
|
||||
const { buildMenuTemplate, ...iconMenuProps } = this.props; //eslint-disable-line
|
||||
|
||||
// Use disableAutoFocus to avoid making TextField lose focus.
|
||||
// See material-ui bug: https://github.com/callemall/material-ui/issues/4387
|
||||
|
@@ -26,6 +26,18 @@ const adaptAcceleratorString = (accelerator: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct items for material-ui's Menu, using a template which
|
||||
* is partially supporting the Electron Menu API (https://github.com/electron/electron/blob/master/docs/api/menu-item.md).
|
||||
*
|
||||
* Supported options are:
|
||||
* - click
|
||||
* - type ('separator' and 'checkbox')
|
||||
* - label
|
||||
* - accelerator
|
||||
* - enabled
|
||||
* - checked (when `type` is 'checkbox')
|
||||
*/
|
||||
export default class MaterialUIMenuImplementation {
|
||||
constructor({ onClose }) {
|
||||
this._onClose = onClose;
|
||||
@@ -35,24 +47,43 @@ export default class MaterialUIMenuImplementation {
|
||||
return template.map((item, id) => {
|
||||
if (item.type === 'separator') {
|
||||
return <Divider key={'separator' + id} />;
|
||||
} else if (item.type === 'checkbox') {
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
primaryText={item.label}
|
||||
secondaryText={
|
||||
item.accelerator
|
||||
? adaptAcceleratorString(item.accelerator)
|
||||
: undefined
|
||||
}
|
||||
checked={item.checked}
|
||||
insetChildren={!item.checked}
|
||||
disabled={item.enabled === false}
|
||||
onTouchTap={() => {
|
||||
item.click();
|
||||
this._onClose();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
primaryText={item.label}
|
||||
secondaryText={
|
||||
item.accelerator
|
||||
? adaptAcceleratorString(item.accelerator)
|
||||
: undefined
|
||||
}
|
||||
disabled={item.enabled === false}
|
||||
onTouchTap={() => {
|
||||
item.click();
|
||||
this._onClose();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
primaryText={item.label}
|
||||
secondaryText={
|
||||
item.accelerator
|
||||
? adaptAcceleratorString(item.accelerator)
|
||||
: undefined
|
||||
}
|
||||
disabled={item.enabled === false}
|
||||
onTouchTap={() => {
|
||||
item.click();
|
||||
this._onClose();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user