Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/blocks/mrc_call_python_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ export const pythonFromBlock = function(
const functionName = callPythonFunctionBlock.mrcActualFunctionName
? callPythonFunctionBlock.mrcActualFunctionName
: block.getFieldValue(FIELD_FUNCTION_NAME);
code = 'self.robot.' + componentName + '.' + functionName;
code = 'self.' + componentName + '.' + functionName;
break;
}
default:
Expand Down
6 changes: 4 additions & 2 deletions src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ const CLASS_METHOD_DEF = {
this.setStyle(MRC_STYLE_CLASS_BLOCKS);
this.appendStatementInput('STACK').appendField('');
this.mrcParameters = [];
this.setPreviousStatement(false);
this.setNextStatement(false);
},
/**
* Returns the state of this block as a JSON serializable object.
Expand Down Expand Up @@ -353,12 +355,12 @@ export const pythonFromBlock = function (
}
if (block.mrcPythonMethodName == '__init__') {
let class_specific = generator.getClassSpecificForInit();
branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' +
branch = generator.INDENT + 'super.__init__(' + class_specific + ')\n' +
generator.defineClassVariables() + branch;
}
else if (funcName == 'update'){
// Special case for update, to also call the update method of the base class
branch = generator.INDENT + 'self.update()\n' + branch;
branch = generator.INDENT + 'super.update()\n' + branch;
}
if (returnValue) {
returnValue = generator.INDENT + 'return ' + returnValue + '\n';
Expand Down
222 changes: 222 additions & 0 deletions src/blocks/mrc_event_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/**
* @license
* Copyright 2025 Porpoiseful LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Blocks for event handlers
* @author [email protected] (Alan Smith)
*/

import * as Blockly from 'blockly';
import {Order} from 'blockly/python';

import {ExtendedPythonGenerator} from '../editor/extended_python_generator';
import {createFieldFlydown} from '../fields/field_flydown';
import {createFieldNonEditableText} from '../fields/FieldNonEditableText';
import {MRC_STYLE_EVENT_HANDLER} from '../themes/styles';

export const BLOCK_NAME = 'mrc_event_handler';

export enum SenderType {
ROBOT = 'robot',
MECHANISM = 'mechanism',
COMPONENT = 'component'
}

export interface Parameter {
name: string;
type?: string;
}

export type EventHandlerBlock = Blockly.Block & EventHandlerMixin & Blockly.BlockSvg;

interface EventHandlerMixin extends EventHandlerMixinType {
mrcPathOfSender: string;
mrcTypeOfSender: SenderType;
mrcParameters: Parameter[];
}

type EventHandlerMixinType = typeof EVENT_HANDLER;

/** Extra state for serialising event handler blocks. */
export interface EventHandlerExtraState {
pathOfSender: string;
typeOfSender: SenderType;
/** The parameters of the event handler. */
params: Parameter[];
}

const EVENT_HANDLER = {
/**
* Block initialization.
*/
init(this: EventHandlerBlock): void {
this.appendDummyInput('TITLE')
.appendField('When')
.appendField(createFieldNonEditableText('sender'), 'SENDER')
.appendField(createFieldNonEditableText('eventName'), 'EVENT_NAME');
this.appendDummyInput('PARAMS')
.appendField('with');
this.setOutput(false);
this.setStyle(MRC_STYLE_EVENT_HANDLER);
this.appendStatementInput('STACK').appendField('');
this.mrcParameters = [];
this.setPreviousStatement(false);
this.setNextStatement(false);
},

/**
* Returns the state of this block as a JSON serializable object.
*/
saveExtraState(this: EventHandlerBlock): EventHandlerExtraState {
const extraState: EventHandlerExtraState = {
pathOfSender: this.mrcPathOfSender,
typeOfSender: this.mrcTypeOfSender,
params: [],
};

this.mrcParameters.forEach((param) => {
extraState.params.push({
name: param.name,
type: param.type,
});
});

return extraState;
},

/**
* Applies the given state to this block.
*/
loadExtraState(this: EventHandlerBlock, extraState: EventHandlerExtraState): void {
this.mrcParameters = [];
this.mrcPathOfSender = extraState.pathOfSender;
this.mrcTypeOfSender = extraState.typeOfSender;

extraState.params.forEach((param) => {
this.mrcParameters.push({
name: param.name,
type: param.type,
});
});
this.mrcUpdateParams();
},

/**
* Update the block to reflect the newly loaded extra state.
*/
mrcUpdateParams(this: EventHandlerBlock): void {
if (this.mrcParameters.length > 0) {
const input = this.getInput('PARAMS');
if (input) {
this.removeParameterFields(input);
this.mrcParameters.forEach((param) => {
const paramName = `PARAM_${param.name}`;
input.appendField(createFieldFlydown(param.name, false), paramName);
});
}
} else {
this.removeInput('PARAMS', true);
}
},

/**
* Removes parameter fields from the given input.
*/
removeParameterFields(input: Blockly.Input): void {
const fieldsToRemove = input.fieldRow
.filter(field => field.name?.startsWith('PARAM_'))
.map(field => field.name!);

fieldsToRemove.forEach(fieldName => {
input.removeField(fieldName);
});
},
};

export function setup(): void {
Blockly.Blocks[BLOCK_NAME] = EVENT_HANDLER;
}

export function pythonFromBlock(
block: EventHandlerBlock,
generator: ExtendedPythonGenerator,
): string {
const blocklyName = `${block.getFieldValue('SENDER')}_${block.getFieldValue('EVENT_NAME')}`;
const funcName = generator.getProcedureName(blocklyName);

let xfix1 = '';
if (generator.STATEMENT_PREFIX) {
xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block);
}
if (generator.STATEMENT_SUFFIX) {
xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block);
}
if (xfix1) {
xfix1 = generator.prefixLines(xfix1, generator.INDENT);
}

let loopTrap = '';
if (generator.INFINITE_LOOP_TRAP) {
loopTrap = generator.prefixLines(
generator.injectId(generator.INFINITE_LOOP_TRAP, block),
generator.INDENT,
);
}

let branch = '';
if (block.getInput('STACK')) {
branch = generator.statementToCode(block, 'STACK');
}

let returnValue = '';
if (block.getInput('RETURN')) {
returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || '';
}

let xfix2 = '';
if (branch && returnValue) {
// After executing the function body, revisit this block for the return.
xfix2 = xfix1;
}

if (returnValue) {
returnValue = `${generator.INDENT}return ${returnValue}\n`;
} else if (!branch) {
branch = generator.PASS;
}

const params = block.mrcParameters;
let paramString = 'self';

if (params.length !== 0) {
block.mrcParameters.forEach((param) => {
paramString += `, ${param.name}`;
});
}

let code = `def ${funcName}(${paramString}):\n`;
code += xfix1 + loopTrap + branch + xfix2 + returnValue;
code = generator.scrub_(block, code);

generator.addClassMethodDefinition(funcName, code);
generator.addEventHandler(
block.getFieldValue('SENDER'),
block.getFieldValue('EVENT_NAME'),
funcName);

return '';
}
18 changes: 14 additions & 4 deletions src/blocks/mrc_get_parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {ExtendedPythonGenerator} from '../editor/extended_python_generator';
import {createFieldNonEditableText} from '../fields/FieldNonEditableText';
import {MRC_STYLE_VARIABLES} from '../themes/styles';
import {BLOCK_NAME as MRC_CLASS_METHOD_DEF, ClassMethodDefBlock} from './mrc_class_method_def';
import {BLOCK_NAME as MRC_EVENT_HANDLER, EventHandlerBlock} from './mrc_event_handler';
import * as ChangeFramework from './utils/change_framework';


Expand All @@ -51,7 +52,7 @@ const GET_PARAMETER_BLOCK = {
.appendField('parameter')
.appendField(createFieldNonEditableText('parameter'), 'PARAMETER_NAME');

this.setOutput(true, [OUTPUT_NAME, this.parameterType]);
this.setOutput(true, this.parameterType);
ChangeFramework.registerCallback(BLOCK_NAME, [Blockly.Events.BLOCK_MOVE], this.onBlockChanged);
},
setNameAndType: function(this: GetParameterBlock, name: string, type: string): void {
Expand All @@ -64,9 +65,8 @@ const GET_PARAMETER_BLOCK = {
const blockBlock = block as Blockly.Block;

if (blockEvent.type === Blockly.Events.BLOCK_MOVE) {
const parent = ChangeFramework.getParentOfType(block, MRC_CLASS_METHOD_DEF);

if (parent) {
let parent = blockBlock.getRootBlock();
if( parent.type === MRC_CLASS_METHOD_DEF) {
// It is a class method definition, so we see if this variable is in it.
const classMethodDefBlock = parent as ClassMethodDefBlock;
for (const parameter of classMethodDefBlock.mrcParameters) {
Expand All @@ -77,6 +77,16 @@ const GET_PARAMETER_BLOCK = {
}
}
}
else if (parent.type === MRC_EVENT_HANDLER) {
const classMethodDefBlock = parent as ClassMethodDefBlock;
for (const parameter of classMethodDefBlock.mrcParameters) {
if (parameter.name === blockBlock.getFieldValue('PARAMETER_NAME')) {
// If it is, we allow it to stay.
blockBlock.setWarningText(null);
return;
}
}
}
// If we end up here it shouldn't be allowed
block.unplug(true);
blockBlock.setWarningText('Parameters can only go in their method\'s block.');
Expand Down
4 changes: 3 additions & 1 deletion src/blocks/setup_custom_blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as OpModeDetails from './mrc_opmode_details';
import * as Event from './mrc_event';
import * as GetParameter from './mrc_get_parameter';
import * as ParameterMutator from './mrc_param_container'
import * as EventHandler from './mrc_event_handler';

const customBlocks = [
CallPythonFunction,
Expand All @@ -34,7 +35,8 @@ const customBlocks = [
OpModeDetails,
Event,
GetParameter,
ParameterMutator
ParameterMutator,
EventHandler,
];

export const setup = function(forBlock: any) {
Expand Down
5 changes: 5 additions & 0 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as toolboxRobot from '../toolbox/toolbox_robot';
//import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests';
import { MethodsCategory} from '../toolbox/methods_category';
import { EventsCategory} from '../toolbox/event_category';
import { ComponentsCategory } from '../toolbox/components_category';


const EMPTY_TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = {
Expand All @@ -46,6 +47,7 @@ export class Editor {
private storage: commonStorage.Storage;
private methodsCategory: MethodsCategory;
private eventsCategory: EventsCategory;
private componentsCategory: ComponentsCategory;
private currentModule: commonStorage.Module | null = null;
private modulePath: string = '';
private projectPath: string = '';
Expand All @@ -61,6 +63,7 @@ export class Editor {
this.storage = storage;
this.methodsCategory = new MethodsCategory(blocklyWorkspace);
this.eventsCategory = new EventsCategory(blocklyWorkspace);
this.componentsCategory = new ComponentsCategory(blocklyWorkspace);
}

private onChangeWhileLoading(event: Blockly.Events.Abstract) {
Expand Down Expand Up @@ -124,6 +127,8 @@ export class Editor {
this.currentModule = currentModule;
this.methodsCategory.setCurrentModule(currentModule);
this.eventsCategory.setCurrentModule(currentModule);
this.componentsCategory.setCurrentModule(currentModule);

if (currentModule) {
this.modulePath = currentModule.modulePath;
this.projectPath = commonStorage.makeProjectPath(currentModule.projectName);
Expand Down
Loading