Skip to content

Commit e8949a6

Browse files
committed
Adds event support
1 parent f6de4aa commit e8949a6

12 files changed

+421
-164
lines changed

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import * as clientSideStorage from './storage/client_side_storage';
4646
import * as CustomBlocks from './blocks/setup_custom_blocks';
4747
import { initialize as initializePythonBlocks } from './blocks/utils/python';
4848
import * as ChangeFramework from './blocks/utils/change_framework'
49-
import { mutatorOpenListener } from './blocks/mrc_class_method_def'
49+
import { mutatorOpenListener } from './blocks/mrc_param_container'
5050

5151
const App: React.FC = () => {
5252
const [alertErrorMessage, setAlertErrorMessage] = React.useState('');

src/blocks/mrc_call_python_function.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,17 @@ import * as toolboxItems from '../toolbox/items';
4040

4141
export const BLOCK_NAME = 'mrc_call_python_function';
4242

43-
enum FunctionKind {
43+
export enum FunctionKind {
4444
MODULE = 'module',
4545
STATIC = 'static',
4646
CONSTRUCTOR = 'constructor',
4747
INSTANCE = 'instance',
4848
INSTANCE_WITHIN = 'instance_within',
49-
INSTANCE_COMPONENT = 'instance_component'
49+
INSTANCE_COMPONENT = 'instance_component',
50+
EVENT = 'event',
5051
}
5152

52-
const RETURN_TYPE_NONE = 'None';
53+
export const RETURN_TYPE_NONE = 'None';
5354

5455
const FIELD_MODULE_OR_CLASS_NAME = 'MODULE_OR_CLASS';
5556
const FIELD_FUNCTION_NAME = 'FUNC';
@@ -318,6 +319,11 @@ const CALL_PYTHON_FUNCTION = {
318319
tooltip = 'Calls the instance method ' + functionName + '.';
319320
break;
320321
}
322+
case FunctionKind.EVENT: {
323+
const functionName = this.getFieldValue(FIELD_FUNCTION_NAME);
324+
tooltip = 'Fires the event ' + functionName + '.';
325+
break;
326+
}
321327
case FunctionKind.INSTANCE_COMPONENT: {
322328
const className = this.mrcComponentClassName;
323329
const functionName = this.getFieldValue(FIELD_FUNCTION_NAME);
@@ -459,6 +465,15 @@ const CALL_PYTHON_FUNCTION = {
459465
}
460466
break;
461467
}
468+
case FunctionKind.EVENT: {
469+
const input = this.getInput('TITLE');
470+
if (!input) {
471+
this.appendDummyInput('TITLE')
472+
.appendField('fire')
473+
.appendField(createFieldNonEditableText(''), FIELD_FUNCTION_NAME);
474+
}
475+
break;
476+
}
462477
case FunctionKind.INSTANCE_COMPONENT: {
463478
const componentNames = Editor.getComponentNames(this.workspace, this.mrcComponentClassName);
464479
const componentName = this.getComponentName();
@@ -586,6 +601,12 @@ export const pythonFromBlock = function(
586601
code = 'self.' + functionName;
587602
break;
588603
}
604+
case FunctionKind.EVENT: {
605+
const blocklyName = block.getFieldValue(FIELD_FUNCTION_NAME);
606+
const functionName = generator.getProcedureName(blocklyName);
607+
code = 'if self.events.get("' + functionName + '", None):\n' + generator.INDENT + 'self.events["' + functionName + '"]';
608+
break;
609+
}
589610
case FunctionKind.INSTANCE_COMPONENT: {
590611
const componentName = callPythonFunctionBlock.getComponentName();
591612
const functionName = callPythonFunctionBlock.mrcActualFunctionName

src/blocks/mrc_class_method_def.ts

Lines changed: 1 addition & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,15 @@ import * as Blockly from 'blockly';
2323
import { MRC_STYLE_CLASS_BLOCKS } from '../themes/styles';
2424
import { createFieldNonEditableText } from '../fields/FieldNonEditableText'
2525
import { createFieldFlydown } from '../fields/field_flydown';
26-
import * as ChangeFramework from './utils/change_framework'
27-
import { getLegalName } from './utils/python';
2826
import { Order } from 'blockly/python';
2927
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
3028
import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function'
3129
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
3230
import { BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter';
33-
31+
import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock } from './mrc_param_container'
3432

3533
export const BLOCK_NAME = 'mrc_class_method_def';
3634

37-
export const MUTATOR_BLOCK_NAME = 'methods_mutatorarg';
38-
const PARAM_CONTAINER_BLOCK_NAME = 'method_param_container';
39-
4035
export type Parameter = {
4136
name: string,
4237
type?: string,
@@ -314,133 +309,8 @@ function isMethodNameUsed(
314309
return false;
315310
}
316311

317-
const METHOD_PARAM_CONTAINER = {
318-
init: function (this: Blockly.Block) {
319-
this.appendDummyInput("TITLE").appendField('Parameters');
320-
this.appendStatementInput('STACK');
321-
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
322-
this.contextMenu = false;
323-
},
324-
};
325-
326-
type MethodMutatorArgBlock = Blockly.Block & MethodMutatorArgMixin & Blockly.BlockSvg;
327-
interface MethodMutatorArgMixin extends MethodMutatorArgMixinType {
328-
originalName: string,
329-
}
330-
331-
type MethodMutatorArgMixinType = typeof METHODS_MUTATORARG;
332-
333-
function setName(block: Blockly.BlockSvg) {
334-
const parentBlock = ChangeFramework.getParentOfType(block, PARAM_CONTAINER_BLOCK_NAME);
335-
if (parentBlock) {
336-
const variableBlocks = parentBlock!.getDescendants(true)
337-
const otherNames: string[] = []
338-
variableBlocks?.forEach(function (variableBlock) {
339-
if (variableBlock != block) {
340-
otherNames.push(variableBlock.getFieldValue('NAME'));
341-
}
342-
});
343-
const currentName = block.getFieldValue('NAME');
344-
block.setFieldValue(getLegalName(currentName, otherNames), 'NAME');
345-
updateMutatorFlyout(block.workspace);
346-
}
347-
}
348-
349-
const METHODS_MUTATORARG = {
350-
init: function (this: MethodMutatorArgBlock) {
351-
this.appendDummyInput()
352-
.appendField(new Blockly.FieldTextInput(Blockly.Procedures.DEFAULT_ARG), 'NAME');
353-
this.setPreviousStatement(true);
354-
this.setNextStatement(true);
355-
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
356-
this.originalName = '';
357-
this.contextMenu = false;
358-
ChangeFramework.registerCallback(MUTATOR_BLOCK_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);
359-
},
360-
onBlockChanged: function (block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase) {
361-
if (blockEvent.type == Blockly.Events.BLOCK_MOVE) {
362-
let blockMoveEvent = blockEvent as Blockly.Events.BlockMove;
363-
if (blockMoveEvent.reason?.includes('connect')) {
364-
setName(block);
365-
}
366-
}
367-
else {
368-
if (blockEvent.type == Blockly.Events.BLOCK_CHANGE) {
369-
setName(block);
370-
}
371-
}
372-
},
373-
}
374-
375-
376-
/**
377-
* Updates the procedure mutator's flyout so that the arg block is not a
378-
* duplicate of another arg.
379-
*
380-
* @param workspace The procedure mutator's workspace. This workspace's flyout
381-
* is what is being updated.
382-
*/
383-
function updateMutatorFlyout(workspace: Blockly.WorkspaceSvg) {
384-
const usedNames = [];
385-
const blocks = workspace.getBlocksByType(MUTATOR_BLOCK_NAME, false);
386-
for (let i = 0, block; (block = blocks[i]); i++) {
387-
usedNames.push(block.getFieldValue('NAME'));
388-
}
389-
const argValue = Blockly.Variables.generateUniqueNameFromOptions(
390-
Blockly.Procedures.DEFAULT_ARG,
391-
usedNames,
392-
);
393-
const jsonBlock = {
394-
kind: 'block',
395-
type: MUTATOR_BLOCK_NAME,
396-
fields: {
397-
NAME: argValue,
398-
},
399-
};
400-
401-
workspace.updateToolbox({ contents: [jsonBlock] });
402-
}
403-
404-
405-
/**
406-
* Listens for when a procedure mutator is opened. Then it triggers a flyout
407-
* update and adds a mutator change listener to the mutator workspace.
408-
*
409-
* @param e The event that triggered this listener.
410-
* @internal
411-
*/
412-
export function mutatorOpenListener(e: Blockly.Events.Abstract) {
413-
if (e.type != Blockly.Events.BUBBLE_OPEN) {
414-
return;
415-
}
416-
const bubbleEvent = e as Blockly.Events.BubbleOpen;
417-
if (
418-
!(bubbleEvent.bubbleType === 'mutator' && bubbleEvent.isOpen) ||
419-
!bubbleEvent.blockId
420-
) {
421-
return;
422-
}
423-
const workspaceId = bubbleEvent.workspaceId;
424-
const block = Blockly.common
425-
.getWorkspaceById(workspaceId)!
426-
.getBlockById(bubbleEvent.blockId) as Blockly.BlockSvg;
427-
428-
if (block.type !== BLOCK_NAME) {
429-
return;
430-
}
431-
const workspace = (
432-
block.getIcon(Blockly.icons.MutatorIcon.TYPE) as Blockly.icons.MutatorIcon
433-
).getWorkspace()!;
434-
435-
updateMutatorFlyout(workspace);
436-
ChangeFramework.setup(workspace);
437-
}
438-
439-
440312
export const setup = function () {
441313
Blockly.Blocks[BLOCK_NAME] = CLASS_METHOD_DEF;
442-
Blockly.Blocks[MUTATOR_BLOCK_NAME] = METHODS_MUTATORARG;
443-
Blockly.Blocks[PARAM_CONTAINER_BLOCK_NAME] = METHOD_PARAM_CONTAINER;
444314
};
445315

446316
export const pythonFromBlock = function (

src/blocks/mrc_event.ts

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import { Order } from 'blockly/python';
2424

2525
import { MRC_STYLE_EVENTS } from '../themes/styles'
2626
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
27-
import { MUTATOR_BLOCK_NAME } from './mrc_class_method_def'
27+
import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock } from './mrc_param_container'
28+
import * as ChangeFramework from './utils/change_framework';
29+
import { BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER } from './mrc_mechanism_component_holder';
2830

2931
export const BLOCK_NAME = 'mrc_event';
3032
export const OUTPUT_NAME = 'mrc_event';
@@ -38,10 +40,10 @@ type EventExtraState = {
3840
params?: Parameter[],
3941
}
4042

41-
type EventBlock = Blockly.Block & EventMixin & Blockly.BlockSvg;
43+
export type EventBlock = Blockly.Block & EventMixin & Blockly.BlockSvg;
4244

4345
interface EventMixin extends EventMixinType {
44-
mrcParams: Parameter[],
46+
mrcParameters: Parameter[],
4547
}
4648
type EventMixinType = typeof EVENT;
4749

@@ -51,11 +53,12 @@ const EVENT = {
5153
*/
5254
init: function (this: EventBlock): void {
5355
this.setStyle(MRC_STYLE_EVENTS);
54-
this.appendDummyInput()
56+
this.appendDummyInput("TITLE")
5557
.appendField(new Blockly.FieldTextInput('my_event'), 'NAME');
5658
this.setPreviousStatement(true, OUTPUT_NAME);
5759
this.setNextStatement(true, OUTPUT_NAME);
5860
this.setMutator(new Blockly.icons.MutatorIcon([MUTATOR_BLOCK_NAME], this));
61+
ChangeFramework.registerCallback(BLOCK_NAME, [Blockly.Events.BLOCK_MOVE], this.onBlockChanged);
5962
},
6063

6164
/**
@@ -65,8 +68,8 @@ const EVENT = {
6568
const extraState: EventExtraState = {
6669
};
6770
extraState.params = [];
68-
if (this.mrcParams) {
69-
this.mrcParams.forEach((arg) => {
71+
if (this.mrcParameters) {
72+
this.mrcParameters.forEach((arg) => {
7073
extraState.params!.push({
7174
'name': arg.name,
7275
'type': arg.type,
@@ -79,24 +82,117 @@ const EVENT = {
7982
* Applies the given state to this block.
8083
*/
8184
loadExtraState: function (this: EventBlock, extraState: EventExtraState): void {
82-
this.mrcParams = [];
85+
this.mrcParameters = [];
8386

8487
if (extraState.params) {
8588
extraState.params.forEach((arg) => {
86-
this.mrcParams.push({
89+
this.mrcParameters.push({
8790
'name': arg.name,
8891
'type': arg.type,
8992
});
9093
});
9194
}
92-
this.mrcParams = extraState.params ? extraState.params : [];
95+
this.mrcParameters = extraState.params ? extraState.params : [];
9396
this.updateBlock_();
9497
},
9598
/**
9699
* Update the block to reflect the newly loaded extra state.
97100
*/
98101
updateBlock_: function (this: EventBlock): void {
99-
}
102+
const name = this.getFieldValue('NAME');
103+
const input = this.getInput('TITLE');
104+
if (!input) {
105+
return;
106+
}
107+
input.removeField('NAME');
108+
109+
const nameField = new Blockly.FieldTextInput(name);
110+
input.insertFieldAt(0, nameField, 'NAME');
111+
this.setMutator(new Blockly.icons.MutatorIcon([MUTATOR_BLOCK_NAME], this));
112+
// nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField));
113+
114+
this.mrcUpdateParams();
115+
},
116+
compose: function (this: EventBlock, containerBlock: any) {
117+
// Parameter list.
118+
this.mrcParameters = [];
119+
120+
let paramBlock = containerBlock.getInputTargetBlock('STACK');
121+
while (paramBlock) {
122+
const param: Parameter = {
123+
name: paramBlock.getFieldValue('NAME'),
124+
type: ''
125+
}
126+
if (paramBlock.originalName) {
127+
// This is a mutator arg block, so we can get the original name.
128+
paramBlock.originalName = param.name;
129+
}
130+
this.mrcParameters.push(param);
131+
paramBlock =
132+
paramBlock.nextConnection && paramBlock.nextConnection.targetBlock();
133+
}
134+
this.mrcUpdateParams();
135+
//mutateMethodCallers(this.workspace, this.getFieldValue('NAME'), this.saveExtraState());
136+
},
137+
decompose: function (this: EventBlock, workspace: Blockly.Workspace) {
138+
// This is a special sub-block that only gets created in the mutator UI.
139+
// It acts as our "top block"
140+
const topBlock = workspace.newBlock(PARAM_CONTAINER_BLOCK_NAME);
141+
(topBlock as Blockly.BlockSvg).initSvg();
142+
143+
// Then we add one sub-block for each item in the list.
144+
let connection = topBlock!.getInput('STACK')!.connection;
145+
146+
for (let i = 0; i < this.mrcParameters.length; i++) {
147+
let itemBlock = workspace.newBlock(MUTATOR_BLOCK_NAME);
148+
(itemBlock as Blockly.BlockSvg).initSvg();
149+
itemBlock.setFieldValue(this.mrcParameters[i].name, 'NAME');
150+
(itemBlock as MethodMutatorArgBlock).originalName = this.mrcParameters[i].name;
151+
152+
connection!.connect(itemBlock.previousConnection!);
153+
connection = itemBlock.nextConnection;
154+
}
155+
return topBlock;
156+
},
157+
mrcUpdateParams: function (this: EventBlock) {
158+
if (this.mrcParameters.length > 0) {
159+
let input = this.getInput('TITLE');
160+
if (input) {
161+
this.removeParameterFields(input);
162+
this.mrcParameters.forEach((param) => {
163+
const paramName = 'PARAM_' + param.name;
164+
const field = new Blockly.FieldTextInput(param.name);
165+
field.EDITABLE = false;
166+
input.appendField(field, paramName);
167+
});
168+
}
169+
}
170+
},
171+
removeParameterFields: function (input: Blockly.Input) {
172+
const fieldsToRemove = input.fieldRow
173+
.filter(field => field.name?.startsWith('PARAM_'))
174+
.map(field => field.name!);
175+
176+
fieldsToRemove.forEach(fieldName => {
177+
input.removeField(fieldName);
178+
});
179+
},
180+
onBlockChanged(block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase): void {
181+
const blockBlock = block as Blockly.Block;
182+
183+
if (blockEvent.type === Blockly.Events.BLOCK_MOVE) {
184+
const parent = ChangeFramework.getParentOfType(block, MRC_MECHANISM_COMPONENT_HOLDER);
185+
186+
if (parent) {
187+
// If it is, we allow it to stay.
188+
blockBlock.setWarningText(null);
189+
return;
190+
}
191+
// If we end up here it shouldn't be allowed
192+
block.unplug(true);
193+
blockBlock.setWarningText('Events can only go in the events block');
194+
}
195+
},
100196
}
101197

102198
export const setup = function () {

0 commit comments

Comments
 (0)