diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index a32b4b7a..e7702aaa 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -329,7 +329,7 @@ export const pythonFromBlock = function( if (block.outputConnection) { return [code, Order.FUNCTION_CALL]; } else { - return code; + '\n'; + return code + '\n'; } }; diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 1bb367e6..9b2b5d6a 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -45,6 +45,8 @@ export type Parameter = { type ClassMethodDefBlock = Blockly.Block & ClassMethodDefMixin & Blockly.BlockSvg; interface ClassMethodDefMixin extends ClassMethodDefMixinType { mrcCanChangeSignature: boolean, + mrcCanBeCalledWithinClass: boolean, + mrcCanBeCalledOutsideClass: boolean, mrcReturnType: string, mrcParameters: Parameter[], mrcPythonMethodName: string, @@ -57,6 +59,14 @@ type ClassMethodDefExtraState = { * Can change name and parameters and return type */ canChangeSignature: boolean, + /** + * Can be called from within the class. + */ + canBeCalledWithinClass: boolean, + /** + * Can be called from outside the class. + */ + canBeCalledOutsideClass: boolean, /** * The return type of the function. * Use 'None' for no return value. @@ -95,6 +105,8 @@ const CLASS_METHOD_DEF = { this: ClassMethodDefBlock): ClassMethodDefExtraState { const extraState: ClassMethodDefExtraState = { canChangeSignature: this.mrcCanChangeSignature, + canBeCalledWithinClass: this.mrcCanBeCalledWithinClass, + canBeCalledOutsideClass: this.mrcCanBeCalledOutsideClass, returnType: this.mrcReturnType, params: [], }; @@ -118,6 +130,8 @@ const CLASS_METHOD_DEF = { extraState: ClassMethodDefExtraState ): void { this.mrcCanChangeSignature = extraState.canChangeSignature; + this.mrcCanBeCalledWithinClass = extraState.canBeCalledWithinClass; + this.mrcCanBeCalledOutsideClass = extraState.canBeCalledOutsideClass; this.mrcPythonMethodName = extraState.pythonMethodName ? extraState.pythonMethodName : ''; this.mrcReturnType = extraState.returnType; this.mrcParameters = []; diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 6530e35f..28c6273f 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -24,6 +24,7 @@ import * as Blockly from 'blockly/core'; import { extendedPythonGenerator } from './extended_python_generator'; import * as commonStorage from '../storage/common_storage'; import { getToolboxJSON } from '../toolbox/toolbox'; +import { MethodsCategory} from '../toolbox/methods_category'; const EMPTY_TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = { @@ -34,6 +35,7 @@ const EMPTY_TOOLBOX: Blockly.utils.toolbox.ToolboxDefinition = { export class Editor { private blocklyWorkspace: Blockly.WorkspaceSvg; private storage: commonStorage.Storage; + private methodsCategory: MethodsCategory; private currentModule: commonStorage.Module | null = null; private modulePath: string = ''; private projectPath: string = ''; @@ -45,6 +47,7 @@ export class Editor { constructor(blocklyWorkspace: Blockly.WorkspaceSvg, storage: commonStorage.Storage) { this.blocklyWorkspace = blocklyWorkspace; this.storage = storage; + this.methodsCategory = new MethodsCategory(blocklyWorkspace); } private onChangeWhileLoading(event: Blockly.Events.Abstract) { @@ -105,6 +108,7 @@ export class Editor { public async loadModuleBlocks(currentModule: commonStorage.Module | null) { this.currentModule = currentModule; + this.methodsCategory.setCurrentModule(currentModule); if (currentModule) { this.modulePath = currentModule.modulePath; this.projectPath = commonStorage.makeProjectPath(currentModule.projectName); @@ -219,4 +223,5 @@ export class Editor { throw e; } } + } diff --git a/src/modules/mechanism_start.json b/src/modules/mechanism_start.json index 87cc9057..fa26e3bb 100644 --- a/src/modules/mechanism_start.json +++ b/src/modules/mechanism_start.json @@ -10,6 +10,8 @@ "deletable": false, "extraState": { "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, "returnType": "None", "params": [], "pythonMethodName": "__init__" @@ -26,6 +28,8 @@ "deletable": false, "extraState": { "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, "returnType": "None", "params": [] }, @@ -35,4 +39,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/modules/opmode_start.json b/src/modules/opmode_start.json index 6abbebfd..bd763f17 100644 --- a/src/modules/opmode_start.json +++ b/src/modules/opmode_start.json @@ -10,6 +10,8 @@ "deletable": false, "extraState": { "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, "returnType": "None", "params": [], "pythonMethodName": "__init__" @@ -26,6 +28,8 @@ "deletable": false, "extraState": { "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, "returnType": "None", "params": [] }, @@ -35,4 +39,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/modules/robot_start.json b/src/modules/robot_start.json index 0f5391af..6452b44e 100644 --- a/src/modules/robot_start.json +++ b/src/modules/robot_start.json @@ -10,6 +10,8 @@ "deletable": false, "extraState": { "canChangeSignature": false, + "canBeCalledWithinClass": false, + "canBeCalledOutsideClass": false, "returnType": "None", "params": [], "pythonMethodName": "__init__" @@ -20,4 +22,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/themes/styles.ts b/src/themes/styles.ts index 66384719..cf750cbc 100644 --- a/src/themes/styles.ts +++ b/src/themes/styles.ts @@ -26,20 +26,21 @@ export const MRC_STYLE_ENUM = 'mrc_style_enum'; export const MRC_STYLE_VARIABLES = 'mrc_style_variables'; export const MRC_STYLE_COMMENTS = 'mrc_style_comments'; export const MRC_STYLE_MISC = 'mrc_style_misc'; -export const MRC_STYLE_CLASS_BLOCKS = 'mrc_style_class_blocks' +export const MRC_STYLE_CLASS_BLOCKS = 'mrc_style_class_blocks'; +export const MRC_CATEGORY_STYLE_METHODS = 'mrc_category_style_methods'; export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { theme.setBlockStyle(MRC_STYLE_FUNCTIONS,{ colourPrimary: "#805ba5", colourSecondary: "#e6deed", colourTertiary: "#664984", - hat: "" + hat: "" }); theme.setBlockStyle(MRC_STYLE_ENUM,{ colourPrimary: "#5ba5a5", colourSecondary: "#deeded", colourTertiary: "#498484", - hat: "" + hat: "" }); theme.setBlockStyle(MRC_STYLE_VARIABLES, { colourPrimary: "#5ba55b", @@ -53,13 +54,17 @@ export const add_mrc_styles = function(theme : Blockly.Theme) : Blockly.Theme { colourTertiary:"#CDB6E9", hat:"" }); - theme.setBlockStyle(MRC_STYLE_COMMENTS,{ + theme.setCategoryStyle(MRC_CATEGORY_STYLE_METHODS, { + colour: '#4A148C', + }); + + theme.setBlockStyle(MRC_STYLE_COMMENTS, { colourPrimary: "#5b5ba5", colourSecondary: "#dedeed", colourTertiary: "#494984", hat:"" }); - theme.setBlockStyle(MRC_STYLE_MISC,{ + theme.setBlockStyle(MRC_STYLE_MISC, { colourPrimary: "#5b5ba5", colourSecondary: "#dedeed", colourTertiary: "#494984", diff --git a/src/toolbox/mechanism_class_methods.ts b/src/toolbox/mechanism_class_methods.ts new file mode 100644 index 00000000..46146fbf --- /dev/null +++ b/src/toolbox/mechanism_class_methods.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2025 Google 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. + */ + +/** + * @author lizlooney@google.com (Liz Looney) + */ + +import * as toolboxItems from '../toolbox/items'; + + +/** + * The blocks that define the methods for the Mechanism class. + * + * These blocks will be added (if appropriate) to the Methods category in the + * toolbox. + */ +export const mechanism_class_blocks: toolboxItems.Block[] = [ + { + kind: 'block', + type: 'mrc_class_method_def', + fields: { + 'NAME': 'update', + }, + extraState: { + canChangeSignature: false, + canBeCalledWithinClass: false, + canBeCalledOutsideClass: false, + canDelete: false, + returnType: 'None', + params: [], + }, + }, +]; diff --git a/src/toolbox/methods_category.ts b/src/toolbox/methods_category.ts new file mode 100644 index 00000000..e7d882a4 --- /dev/null +++ b/src/toolbox/methods_category.ts @@ -0,0 +1,161 @@ +/** + * @license + * Copyright 2025 Google 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. + */ + +/** + * @author lizlooney@google.com (Liz Looney) + */ + +import * as Blockly from 'blockly/core'; + +import * as commonStorage from '../storage/common_storage'; +import { MRC_CATEGORY_STYLE_METHODS } from '../themes/styles' +import { mechanism_class_blocks } from './mechanism_class_methods'; +import { opmode_class_blocks } from './opmode_class_methods'; +import { robot_class_blocks } from './robot_class_methods'; + + +const CUSTOM_CATEGORY_METHODS = 'METHODS'; + +export const category = { + kind: 'category', + categorystyle: MRC_CATEGORY_STYLE_METHODS, + name: 'Methods', + custom: CUSTOM_CATEGORY_METHODS, +}; + +export class MethodsCategory { + private currentModule: commonStorage.Module | null = null; + + constructor(blocklyWorkspace: Blockly.WorkspaceSvg) { + blocklyWorkspace.registerToolboxCategoryCallback(CUSTOM_CATEGORY_METHODS, this.methodsFlyout.bind(this)); + } + + public setCurrentModule(currentModule: commonStorage.Module | null) { + this.currentModule = currentModule; + } + + public methodsFlyout(workspace: Blockly.WorkspaceSvg): ToolboxInfo { + const toolboxInfo = { + contents: [ + ], + } + + // Add blocks for defining any methods that can be defined in the current + // module. For example, if the current module is an OpMode, add blocks to + // define the methods declared in the OpMode class. + if (this.currentModule) { + // Collect the method names for mrc_class_method_def blocks that are + // already in the blockly workspace. + const methodNamesAlreadyUsed: string[] = []; + workspace.getBlocksByType('mrc_class_method_def', false).forEach((classMethodDefBlock) => { + if (!classMethodDefBlock.mrcCanChangeSignature) { + methodNamesAlreadyUsed.push(classMethodDefBlock.getFieldValue('NAME')); + } + }); + + if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_PROJECT) { + // Add the methods for a Project (Robot). + this.addClassBlocksForCurrentModule( + 'More Robot Methods', robot_class_blocks, + methodNamesAlreadyUsed, toolboxInfo.contents); + } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_MECHANISM) { + // Add the methods for a Mechanism. + this.addClassBlocksForCurrentModule( + 'More Mechanism Methods', mechanism_class_blocks, + methodNamesAlreadyUsed, toolboxInfo.contents); + } else if (this.currentModule.moduleType == commonStorage.MODULE_TYPE_OPMODE) { + // Add the methods for an OpMode. + this.addClassBlocksForCurrentModule( + 'More OpMode Methods', opmode_class_blocks, + methodNamesAlreadyUsed, toolboxInfo.contents); + } + } + + // Add a block that lets the user define a new method. + toolboxInfo.contents.push( + { + kind: 'label', + text: 'Custom Methods', + }, + { + kind: 'block', + type: 'mrc_class_method_def', + fields: {NAME: "my_method"}, + extraState: { + canChangeSignature: true, + canBeCalledWithinClass: true, + canBeCalledOutsideClass: true, + canDelete: true, + returnType: 'None', + params: [], + }, + }); + + // For each mrc_class_method_def block in the blockly workspace, check if it + // can be called from within the class, and if so, add a + // mrc_call_python_function block. + workspace.getBlocksByType('mrc_class_method_def', false).forEach((classMethodDefBlock) => { + if (classMethodDefBlock.mrcCanBeCalledWithinClass) { + const callPythonFunctionBlock = { + kind: 'block', + type: 'mrc_call_python_function', + importModule: '', + extraState: { + functionKind: 'instance_within', + returnType: classMethodDefBlock.mrcReturnType, + args: [], + actualFunctionName: classMethodDefBlock.mrcPythonMethodName, + }, + fields: { + FUNC: classMethodDefBlock.getFieldValue('NAME'), + }, + }; + classMethodDefBlock.mrcParameters.forEach((param) => { + callPythonFunctionBlock.extraState.args.push( + { + name: param.name, + type: param.type, + }); + }); + toolboxInfo.contents.push(callPythonFunctionBlock); + } + }); + + return toolboxInfo; + } + + private addClassBlocksForCurrentModule( + label: string, class_blocks: toolboxItems.Block[], + methodNamesAlreadyUsed: string[], contents: []) { + let labelAdded = false; + class_blocks.forEach((blockInfo) => { + const methodName = blockInfo.fields['NAME']; + if (!methodNamesAlreadyUsed.includes(methodName)) { + if (!labelAdded) { + contents.push( + { + kind: 'label', + text: label, + }, + ); + labelAdded = true; + } + contents.push(blockInfo); + } + }); + } +} diff --git a/src/toolbox/opmode_class_methods.ts b/src/toolbox/opmode_class_methods.ts new file mode 100644 index 00000000..195b7d21 --- /dev/null +++ b/src/toolbox/opmode_class_methods.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2025 Google 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. + */ + +/** + * @author lizlooney@google.com (Liz Looney) + */ + +import * as toolboxItems from '../toolbox/items'; + + +/** + * The blocks that define the methods for the OpMode class. + * + * These blocks will be added (if appropriate) to the Methods category in the + * toolbox. + */ +export const opmode_class_blocks: toolboxItems.Block[] = [ + { + kind: 'block', + type: 'mrc_class_method_def', + fields: { + 'NAME': 'start', + }, + extraState: { + canChangeSignature: false, + canBeCalledWithinClass: false, + canBeCalledOutsideClass: false, + canDelete: true, + returnType: 'None', + params: [], + }, + }, + { + kind: 'block', + type: 'mrc_class_method_def', + fields: { + 'NAME': 'loop', + }, + extraState: { + canChangeSignature: false, + canBeCalledWithinClass: false, + canBeCalledOutsideClass: false, + canDelete: false, + returnType: 'None', + params: [], + }, + }, + { + kind: 'block', + type: 'mrc_class_method_def', + fields: { + 'NAME': 'stop', + }, + extraState: { + canChangeSignature: false, + canBeCalledWithinClass: false, + canBeCalledOutsideClass: false, + canDelete: true, + returnType: 'None', + params: [], + }, + }, +]; diff --git a/src/toolbox/opmode_methods_category.ts b/src/toolbox/opmode_methods_category.ts deleted file mode 100644 index 57d117d9..00000000 --- a/src/toolbox/opmode_methods_category.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { MRC_STYLE_CLASS_BLOCKS } from '../themes/styles' -export const category = -{ - kind: 'category', - style: MRC_STYLE_CLASS_BLOCKS, - name: 'Methods', - contents: [ - { - kind: 'block', - type: 'mrc_class_method_def', - fields: {NAME: "start"}, - extraState: { - canChangeSignature: false, - canDelete: true, - returnType: 'None', - params: [] - }, - }, - { - kind: 'block', - type: 'mrc_class_method_def', - fields: {NAME: "stop"}, - extraState: { - canChangeSignature: false, - canDelete: true, - returnType: 'None', - params: [] - }, - }, - { - kind: 'block', - type: 'mrc_class_method_def', - fields: {NAME: "my_method"}, - extraState: { - canChangeSignature: true, - canDelete: true, - returnType: 'None', - params: [] - } - }, - ], -} \ No newline at end of file diff --git a/src/toolbox/robot_class_methods.ts b/src/toolbox/robot_class_methods.ts new file mode 100644 index 00000000..9b080fb1 --- /dev/null +++ b/src/toolbox/robot_class_methods.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2025 Google 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. + */ + +/** + * @author lizlooney@google.com (Liz Looney) + */ + +import * as toolboxItems from '../toolbox/items'; + + +/** + * The blocks that define the methods for the Robot class. + * + * These blocks will be added (if appropriate) to the Methods category in the + * toolbox. + */ +export const robot_class_blocks: toolboxItems.Block[] = [ + // TODO(lizlooney): Add one mrc_class_method_def blocks for each method in ther Robot class. +]; diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 2346398c..30281c88 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -27,8 +27,7 @@ import {category as mathCategory} from './math_category'; import {category as textCategory} from './text_category'; import {category as listsCategory} from './lists_category'; import {category as miscCategory} from './misc_category'; -import {category as mechanismCategory } from './mechanism_category'; -import {category as methodsCategory} from './opmode_methods_category'; +import {category as methodsCategory} from './methods_category'; export function getToolboxJSON( opt_includeExportedBlocksFromProject: toolboxItems.ContentsType[], @@ -47,9 +46,10 @@ export function getToolboxJSON( kind: 'category', name: 'Project', contents: opt_includeExportedBlocksFromProject, - } + }, ]); } + contents.push.apply( contents, [ @@ -71,8 +71,7 @@ export function getToolboxJSON( categorystyle: 'variable_category', custom: 'VARIABLE', }, - mechanismCategory, - methodsCategory + methodsCategory, ]); return {