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 Blocks for event handlers
20+ * @author [email protected] (Alan Smith) 21+ */
22+
23+ import * as Blockly from 'blockly' ;
24+ import { Order } from 'blockly/python' ;
25+
26+ import { ExtendedPythonGenerator } from '../editor/extended_python_generator' ;
27+ import { createFieldFlydown } from '../fields/field_flydown' ;
28+ import { createFieldNonEditableText } from '../fields/FieldNonEditableText' ;
29+ import { MRC_STYLE_EVENT_HANDLER } from '../themes/styles' ;
30+
31+ export const BLOCK_NAME = 'mrc_event_handler' ;
32+
33+ export enum SenderType {
34+ ROBOT = 'robot' ,
35+ MECHANISM = 'mechanism' ,
36+ COMPONENT = 'component'
37+ }
38+
39+ export interface Parameter {
40+ name : string ;
41+ type ?: string ;
42+ }
43+
44+ export type EventHandlerBlock = Blockly . Block & EventHandlerMixin & Blockly . BlockSvg ;
45+
46+ interface EventHandlerMixin extends EventHandlerMixinType {
47+ mrcPathOfSender : string ;
48+ mrcTypeOfSender : SenderType ;
49+ mrcParameters : Parameter [ ] ;
50+ }
51+
52+ type EventHandlerMixinType = typeof EVENT_HANDLER ;
53+
54+ /** Extra state for serialising event handler blocks. */
55+ export interface EventHandlerExtraState {
56+ pathOfSender : string ;
57+ typeOfSender : SenderType ;
58+ /** The parameters of the event handler. */
59+ params : Parameter [ ] ;
60+ }
61+
62+ const EVENT_HANDLER = {
63+ /**
64+ * Block initialization.
65+ */
66+ init ( this : EventHandlerBlock ) : void {
67+ this . appendDummyInput ( 'TITLE' )
68+ . appendField ( 'When' )
69+ . appendField ( createFieldNonEditableText ( 'sender' ) , 'SENDER' )
70+ . appendField ( createFieldNonEditableText ( 'eventName' ) , 'EVENT_NAME' ) ;
71+ this . appendDummyInput ( 'PARAMS' )
72+ . appendField ( 'with' ) ;
73+ this . setOutput ( false ) ;
74+ this . setStyle ( MRC_STYLE_EVENT_HANDLER ) ;
75+ this . appendStatementInput ( 'STACK' ) . appendField ( '' ) ;
76+ this . mrcParameters = [ ] ;
77+ this . setPreviousStatement ( false ) ;
78+ this . setNextStatement ( false ) ;
79+ } ,
80+
81+ /**
82+ * Returns the state of this block as a JSON serializable object.
83+ */
84+ saveExtraState ( this : EventHandlerBlock ) : EventHandlerExtraState {
85+ const extraState : EventHandlerExtraState = {
86+ pathOfSender : this . mrcPathOfSender ,
87+ typeOfSender : this . mrcTypeOfSender ,
88+ params : [ ] ,
89+ } ;
90+
91+ this . mrcParameters . forEach ( ( param ) => {
92+ extraState . params . push ( {
93+ name : param . name ,
94+ type : param . type ,
95+ } ) ;
96+ } ) ;
97+
98+ return extraState ;
99+ } ,
100+
101+ /**
102+ * Applies the given state to this block.
103+ */
104+ loadExtraState ( this : EventHandlerBlock , extraState : EventHandlerExtraState ) : void {
105+ this . mrcParameters = [ ] ;
106+ this . mrcPathOfSender = extraState . pathOfSender ;
107+ this . mrcTypeOfSender = extraState . typeOfSender ;
108+
109+ extraState . params . forEach ( ( param ) => {
110+ this . mrcParameters . push ( {
111+ name : param . name ,
112+ type : param . type ,
113+ } ) ;
114+ } ) ;
115+ this . mrcUpdateParams ( ) ;
116+ } ,
117+
118+ /**
119+ * Update the block to reflect the newly loaded extra state.
120+ */
121+ mrcUpdateParams ( this : EventHandlerBlock ) : void {
122+ if ( this . mrcParameters . length > 0 ) {
123+ const input = this . getInput ( 'PARAMS' ) ;
124+ if ( input ) {
125+ this . removeParameterFields ( input ) ;
126+ this . mrcParameters . forEach ( ( param ) => {
127+ const paramName = `PARAM_${ param . name } ` ;
128+ input . appendField ( createFieldFlydown ( param . name , false ) , paramName ) ;
129+ } ) ;
130+ }
131+ } else {
132+ this . removeInput ( 'PARAMS' , true ) ;
133+ }
134+ } ,
135+
136+ /**
137+ * Removes parameter fields from the given input.
138+ */
139+ removeParameterFields ( input : Blockly . Input ) : void {
140+ const fieldsToRemove = input . fieldRow
141+ . filter ( field => field . name ?. startsWith ( 'PARAM_' ) )
142+ . map ( field => field . name ! ) ;
143+
144+ fieldsToRemove . forEach ( fieldName => {
145+ input . removeField ( fieldName ) ;
146+ } ) ;
147+ } ,
148+ } ;
149+
150+ export function setup ( ) : void {
151+ Blockly . Blocks [ BLOCK_NAME ] = EVENT_HANDLER ;
152+ }
153+
154+ export function pythonFromBlock (
155+ block : EventHandlerBlock ,
156+ generator : ExtendedPythonGenerator ,
157+ ) : string {
158+ const blocklyName = `${ block . getFieldValue ( 'SENDER' ) } _${ block . getFieldValue ( 'EVENT_NAME' ) } ` ;
159+ const funcName = generator . getProcedureName ( blocklyName ) ;
160+
161+ let xfix1 = '' ;
162+ if ( generator . STATEMENT_PREFIX ) {
163+ xfix1 += generator . injectId ( generator . STATEMENT_PREFIX , block ) ;
164+ }
165+ if ( generator . STATEMENT_SUFFIX ) {
166+ xfix1 += generator . injectId ( generator . STATEMENT_SUFFIX , block ) ;
167+ }
168+ if ( xfix1 ) {
169+ xfix1 = generator . prefixLines ( xfix1 , generator . INDENT ) ;
170+ }
171+
172+ let loopTrap = '' ;
173+ if ( generator . INFINITE_LOOP_TRAP ) {
174+ loopTrap = generator . prefixLines (
175+ generator . injectId ( generator . INFINITE_LOOP_TRAP , block ) ,
176+ generator . INDENT ,
177+ ) ;
178+ }
179+
180+ let branch = '' ;
181+ if ( block . getInput ( 'STACK' ) ) {
182+ branch = generator . statementToCode ( block , 'STACK' ) ;
183+ }
184+
185+ let returnValue = '' ;
186+ if ( block . getInput ( 'RETURN' ) ) {
187+ returnValue = generator . valueToCode ( block , 'RETURN' , Order . NONE ) || '' ;
188+ }
189+
190+ let xfix2 = '' ;
191+ if ( branch && returnValue ) {
192+ // After executing the function body, revisit this block for the return.
193+ xfix2 = xfix1 ;
194+ }
195+
196+ if ( returnValue ) {
197+ returnValue = `${ generator . INDENT } return ${ returnValue } \n` ;
198+ } else if ( ! branch ) {
199+ branch = generator . PASS ;
200+ }
201+
202+ const params = block . mrcParameters ;
203+ let paramString = 'self' ;
204+
205+ if ( params . length !== 0 ) {
206+ block . mrcParameters . forEach ( ( param ) => {
207+ paramString += `, ${ param . name } ` ;
208+ } ) ;
209+ }
210+
211+ let code = `def ${ funcName } (${ paramString } ):\n` ;
212+ code += xfix1 + loopTrap + branch + xfix2 + returnValue ;
213+ code = generator . scrub_ ( block , code ) ;
214+
215+ generator . addClassMethodDefinition ( funcName , code ) ;
216+ generator . addEventHandler (
217+ block . getFieldValue ( 'SENDER' ) ,
218+ block . getFieldValue ( 'EVENT_NAME' ) ,
219+ funcName ) ;
220+
221+ return '' ;
222+ }
0 commit comments