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