Skip to content

Commit f6de4aa

Browse files
authored
Parameters for methods (#120)
* Get rid of holdover * Events in progress * Saw the right way to do this based off field from app inventor * First pass at handling parameters * set the position correctly * ran format code * Makes height dependent on what is in flydown * change to override * Add placeholder for type * Renaming a parameter now updates it in method * Made so you can only drag parameters into a method with that named parameter * Tell Copilot to make this file follow google coding standards
1 parent e6b6160 commit f6de4aa

File tree

11 files changed

+797
-123
lines changed

11 files changed

+797
-123
lines changed

src/blocks/mrc_class_method_def.ts

Lines changed: 138 additions & 114 deletions
Large diffs are not rendered by default.

src/blocks/mrc_event.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Porpoiseful LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @fileoverview Creates an event that can be fired
20+
* @author [email protected] (Alan Smith)
21+
*/
22+
import * as Blockly from 'blockly';
23+
import { Order } from 'blockly/python';
24+
25+
import { MRC_STYLE_EVENTS } from '../themes/styles'
26+
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
27+
import { MUTATOR_BLOCK_NAME } from './mrc_class_method_def'
28+
29+
export const BLOCK_NAME = 'mrc_event';
30+
export const OUTPUT_NAME = 'mrc_event';
31+
32+
export type Parameter = {
33+
name: string,
34+
type?: string,
35+
};
36+
37+
type EventExtraState = {
38+
params?: Parameter[],
39+
}
40+
41+
type EventBlock = Blockly.Block & EventMixin & Blockly.BlockSvg;
42+
43+
interface EventMixin extends EventMixinType {
44+
mrcParams: Parameter[],
45+
}
46+
type EventMixinType = typeof EVENT;
47+
48+
const EVENT = {
49+
/**
50+
* Block initialization.
51+
*/
52+
init: function (this: EventBlock): void {
53+
this.setStyle(MRC_STYLE_EVENTS);
54+
this.appendDummyInput()
55+
.appendField(new Blockly.FieldTextInput('my_event'), 'NAME');
56+
this.setPreviousStatement(true, OUTPUT_NAME);
57+
this.setNextStatement(true, OUTPUT_NAME);
58+
this.setMutator(new Blockly.icons.MutatorIcon([MUTATOR_BLOCK_NAME], this));
59+
},
60+
61+
/**
62+
* Returns the state of this block as a JSON serializable object.
63+
*/
64+
saveExtraState: function (this: EventBlock): EventExtraState {
65+
const extraState: EventExtraState = {
66+
};
67+
extraState.params = [];
68+
if (this.mrcParams) {
69+
this.mrcParams.forEach((arg) => {
70+
extraState.params!.push({
71+
'name': arg.name,
72+
'type': arg.type,
73+
});
74+
});
75+
}
76+
return extraState;
77+
},
78+
/**
79+
* Applies the given state to this block.
80+
*/
81+
loadExtraState: function (this: EventBlock, extraState: EventExtraState): void {
82+
this.mrcParams = [];
83+
84+
if (extraState.params) {
85+
extraState.params.forEach((arg) => {
86+
this.mrcParams.push({
87+
'name': arg.name,
88+
'type': arg.type,
89+
});
90+
});
91+
}
92+
this.mrcParams = extraState.params ? extraState.params : [];
93+
this.updateBlock_();
94+
},
95+
/**
96+
* Update the block to reflect the newly loaded extra state.
97+
*/
98+
updateBlock_: function (this: EventBlock): void {
99+
}
100+
}
101+
102+
export const setup = function () {
103+
Blockly.Blocks[BLOCK_NAME] = EVENT;
104+
}
105+
106+
export const pythonFromBlock = function (
107+
block: EventBlock,
108+
generator: ExtendedPythonGenerator,
109+
) {
110+
//TODO (Alan): What should this do here??
111+
return '';
112+
}

src/blocks/mrc_get_parameter.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Porpoiseful LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @fileoverview This is a block that allows your code to use a parameter
20+
* that is passed to a method.
21+
* @author [email protected] (Alan Smith)
22+
*/
23+
import * as Blockly from 'blockly';
24+
import {Order} from 'blockly/python';
25+
26+
import {ExtendedPythonGenerator} from '../editor/extended_python_generator';
27+
import {createFieldNonEditableText} from '../fields/FieldNonEditableText';
28+
import {MRC_STYLE_VARIABLES} from '../themes/styles';
29+
import {BLOCK_NAME as MRC_CLASS_METHOD_DEF, ClassMethodDefBlock} from './mrc_class_method_def';
30+
import * as ChangeFramework from './utils/change_framework';
31+
32+
33+
export const BLOCK_NAME = 'mrc_get_parameter';
34+
export const OUTPUT_NAME = 'mrc_get_parameter_output';
35+
36+
37+
type GetParameterBlock = Blockly.Block & Blockly.BlockSvg & GetParameterMixin;
38+
39+
interface GetParameterMixin extends GetParameterMixinType {}
40+
41+
type GetParameterMixinType = typeof GET_PARAMETER_BLOCK;
42+
43+
const GET_PARAMETER_BLOCK = {
44+
parameterType: '', // Later this will be set to the type of the parameter, e.g. 'string', 'number', etc.
45+
/**
46+
* Block initialization.
47+
*/
48+
init: function(this: GetParameterBlock): void {
49+
this.setStyle(MRC_STYLE_VARIABLES);
50+
this.appendDummyInput()
51+
.appendField('parameter')
52+
.appendField(createFieldNonEditableText('parameter'), 'PARAMETER_NAME');
53+
54+
this.setOutput(true, [OUTPUT_NAME, this.parameterType]);
55+
ChangeFramework.registerCallback(BLOCK_NAME, [Blockly.Events.BLOCK_MOVE], this.onBlockChanged);
56+
},
57+
setNameAndType: function(this: GetParameterBlock, name: string, type: string): void {
58+
this.setFieldValue(name, 'PARAMETER_NAME');
59+
this.parameterType = type;
60+
this.setOutput(true, [OUTPUT_NAME, type]);
61+
},
62+
63+
onBlockChanged(block: Blockly.BlockSvg, blockEvent: Blockly.Events.BlockBase): void {
64+
const blockBlock = block as Blockly.Block;
65+
66+
if (blockEvent.type === Blockly.Events.BLOCK_MOVE) {
67+
const parent = ChangeFramework.getParentOfType(block, MRC_CLASS_METHOD_DEF);
68+
69+
if (parent) {
70+
// It is a class method definition, so we see if this variable is in it.
71+
const classMethodDefBlock = parent as ClassMethodDefBlock;
72+
for (const parameter of classMethodDefBlock.mrcParameters) {
73+
if (parameter.name === blockBlock.getFieldValue('PARAMETER_NAME')) {
74+
// If it is, we allow it to stay.
75+
blockBlock.setWarningText(null);
76+
return;
77+
}
78+
}
79+
}
80+
// If we end up here it shouldn't be allowed
81+
block.unplug(true);
82+
blockBlock.setWarningText('Parameters can only go in their method\'s block.');
83+
}
84+
},
85+
};
86+
87+
export const setup = function() {
88+
Blockly.Blocks[BLOCK_NAME] = GET_PARAMETER_BLOCK;
89+
};
90+
91+
export const pythonFromBlock = function(
92+
block: GetParameterBlock,
93+
_generator: ExtendedPythonGenerator,
94+
) {
95+
// TODO (Alan) : Specify the type here as well
96+
const code = block.getFieldValue('PARAMETER_NAME');
97+
98+
return [code, Order.ATOMIC];
99+
};

src/blocks/mrc_mechanism_component_holder.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2828
import { OUTPUT_NAME as MECHANISM_OUTPUT } from './mrc_mechanism';
2929
import { BLOCK_NAME as MRC_MECHANISM_NAME } from './mrc_mechanism';
3030
import { BLOCK_NAME as MRC_COMPONENT_NAME } from './mrc_component';
31-
3231
import { OUTPUT_NAME as COMPONENT_OUTPUT } from './mrc_component';
32+
import { BLOCK_NAME as MRC_EVENT_NAME } from './mrc_event';
33+
import { OUTPUT_NAME as EVENT_OUTPUT } from './mrc_event';
3334

3435
export const BLOCK_NAME = 'mrc_mechanism_component_holder';
3536

@@ -69,12 +70,14 @@ const MECHANISM_COMPONENT_HOLDER = {
6970
this.setInputsInline(false);
7071
this.appendStatementInput('MECHANISMS').setCheck(MECHANISM_OUTPUT).appendField('Mechanisms');
7172
this.appendStatementInput('COMPONENTS').setCheck(COMPONENT_OUTPUT).appendField('Components');
73+
this.appendStatementInput('EVENTS').setCheck(EVENT_OUTPUT).appendField('Events');
74+
7275

7376
this.setOutput(false);
7477
this.setStyle(MRC_STYLE_MECHANISMS);
7578
ChangeFramework.registerCallback(MRC_COMPONENT_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);
7679
ChangeFramework.registerCallback(MRC_MECHANISM_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);
77-
80+
ChangeFramework.registerCallback(MRC_EVENT_NAME, [Blockly.Events.BLOCK_MOVE, Blockly.Events.BLOCK_CHANGE], this.onBlockChanged);
7881
},
7982
saveExtraState: function (this: MechanismComponentHolderBlock): MechanismComponentHolderExtraState {
8083
const extraState: MechanismComponentHolderExtraState = {
@@ -140,7 +143,7 @@ function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator:
140143
if (body != '') {
141144
code += body;
142145
}
143-
146+
144147
generator.addClassMethodDefinition('define_hardware', code);
145148
}
146149

src/blocks/setup_custom_blocks.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import * as Component from './mrc_component';
1313
import * as MechanismContainerHolder from './mrc_mechanism_component_holder';
1414
import * as Port from './mrc_port';
1515
import * as OpModeDetails from './mrc_opmode_details';
16+
import * as Event from './mrc_event';
17+
import * as GetParameter from './mrc_get_parameter';
1618

1719
const customBlocks = [
1820
CallPythonFunction,
@@ -28,7 +30,9 @@ const customBlocks = [
2830
Component,
2931
MechanismContainerHolder,
3032
Port,
31-
OpModeDetails
33+
OpModeDetails,
34+
Event,
35+
GetParameter
3236
];
3337

3438
export const setup = function(forBlock: any) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Porpoiseful LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @fileoverview Allow registering for specific callbacks
20+
* @author [email protected] (Alan Smith)
21+
*/
22+
import * as Blockly from 'blockly';
23+
24+
export function findConnectedBlocksOfType(block: Blockly.Block, targetType: string): Blockly.Block[] {
25+
const foundBlocks: Blockly.Block[] = [];
26+
const visited = new Set<string>(); // Prevent infinite loops
27+
28+
function searchRecursive(currentBlock: Blockly.Block): void {
29+
if (visited.has(currentBlock.id)) return;
30+
visited.add(currentBlock.id);
31+
32+
// Check if current block matches target type
33+
if (currentBlock.type === targetType) {
34+
foundBlocks.push(currentBlock);
35+
}
36+
37+
// Search through all inputs
38+
currentBlock.inputList.forEach(input => {
39+
if (input.connection && input.connection.isConnected()) {
40+
const connectedBlock = input.connection.targetBlock();
41+
if (connectedBlock) {
42+
searchRecursive(connectedBlock);
43+
}
44+
}
45+
});
46+
}
47+
48+
searchRecursive(block);
49+
return foundBlocks;
50+
}

src/fields/FieldNonEditableText.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import * as Blockly from 'blockly/core';
2424
class FieldNonEditableText extends Blockly.FieldTextInput {
2525
constructor(value: string) {
2626
super(value);
27-
this.CURSOR = '';
27+
this.EDITABLE = false; // This field is not editable
2828
}
2929

3030
protected override showEditor_() {

0 commit comments

Comments
 (0)