@@ -24,7 +24,6 @@ import * as Blockly from 'blockly';
2424import { MRC_STYLE_EVENTS } from '../themes/styles'
2525import { ExtendedPythonGenerator } from '../editor/extended_python_generator' ;
2626import { MUTATOR_BLOCK_NAME , PARAM_CONTAINER_BLOCK_NAME , MethodMutatorArgBlock } from './mrc_param_container'
27- import * as ChangeFramework from './utils/change_framework' ;
2827import { BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER } from './mrc_mechanism_component_holder' ;
2928import * as toolboxItems from '../toolbox/items' ;
3029import * as storageModuleContent from '../storage/module_content' ;
@@ -33,13 +32,17 @@ import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_func
3332export const BLOCK_NAME = 'mrc_event' ;
3433export const OUTPUT_NAME = 'mrc_event' ;
3534
35+ const INPUT_TITLE = 'TITLE' ;
3636const FIELD_EVENT_NAME = 'NAME' ;
37+ const FIELD_PARAM_PREFIX = 'PARAM_' ;
3738
3839type Parameter = {
3940 name : string ,
4041 type ?: string ,
4142} ;
4243
44+ const WARNING_ID_NOT_IN_HOLDER = 'not in holder' ;
45+
4346type EventExtraState = {
4447 eventId ?: string ,
4548 params ?: Parameter [ ] ,
@@ -50,6 +53,14 @@ export type EventBlock = Blockly.Block & EventMixin & Blockly.BlockSvg;
5053interface EventMixin extends EventMixinType {
5154 mrcEventId : string ,
5255 mrcParameters : Parameter [ ] ,
56+
57+ /**
58+ * mrcHasWarning is set to true if we set the warning text on the block. It is checked to avoid
59+ * adding a warning if there already is one. Otherwise, if we get two move events (one for drag
60+ * and one for snap), and we call setWarningText for both events, we get a detached warning
61+ * balloon. See https://github.com/wpilibsuite/systemcore-blocks-interface/issues/248.
62+ */
63+ mrcHasWarning : boolean ,
5364}
5465type EventMixinType = typeof EVENT ;
5566
@@ -59,12 +70,11 @@ const EVENT = {
5970 */
6071 init : function ( this : EventBlock ) : void {
6172 this . setStyle ( MRC_STYLE_EVENTS ) ;
62- this . appendDummyInput ( "TITLE" )
73+ this . appendDummyInput ( INPUT_TITLE )
6374 . appendField ( new Blockly . FieldTextInput ( 'my_event' ) , FIELD_EVENT_NAME ) ;
6475 this . setPreviousStatement ( true , OUTPUT_NAME ) ;
6576 this . setNextStatement ( true , OUTPUT_NAME ) ;
6677 this . setMutator ( new Blockly . icons . MutatorIcon ( [ MUTATOR_BLOCK_NAME ] , this ) ) ;
67- ChangeFramework . registerCallback ( BLOCK_NAME , [ Blockly . Events . BLOCK_MOVE ] , this . onBlockChanged ) ;
6878 } ,
6979
7080 /**
@@ -91,6 +101,7 @@ const EVENT = {
91101 loadExtraState : function ( this : EventBlock , extraState : EventExtraState ) : void {
92102 this . mrcEventId = extraState . eventId ? extraState . eventId : this . id ;
93103 this . mrcParameters = [ ] ;
104+ this . mrcHasWarning = false ;
94105
95106 if ( extraState . params ) {
96107 extraState . params . forEach ( ( arg ) => {
@@ -108,7 +119,7 @@ const EVENT = {
108119 */
109120 updateBlock_ : function ( this : EventBlock ) : void {
110121 const name = this . getFieldValue ( FIELD_EVENT_NAME ) ;
111- const input = this . getInput ( 'TITLE' ) ;
122+ const input = this . getInput ( INPUT_TITLE ) ;
112123 if ( ! input ) {
113124 return ;
114125 }
@@ -136,8 +147,7 @@ const EVENT = {
136147 paramBlock . originalName = param . name ;
137148 }
138149 this . mrcParameters . push ( param ) ;
139- paramBlock =
140- paramBlock . nextConnection && paramBlock . nextConnection . targetBlock ( ) ;
150+ paramBlock = paramBlock . nextConnection && paramBlock . nextConnection . targetBlock ( ) ;
141151 }
142152 this . mrcUpdateParams ( ) ;
143153 mutateMethodCallers ( this . workspace , this . mrcEventId , this . getEvent ( ) ) ;
@@ -152,7 +162,7 @@ const EVENT = {
152162 let connection = topBlock ! . getInput ( 'STACK' ) ! . connection ;
153163
154164 for ( let i = 0 ; i < this . mrcParameters . length ; i ++ ) {
155- let itemBlock = workspace . newBlock ( MUTATOR_BLOCK_NAME ) ;
165+ const itemBlock = workspace . newBlock ( MUTATOR_BLOCK_NAME ) ;
156166 ( itemBlock as Blockly . BlockSvg ) . initSvg ( ) ;
157167 itemBlock . setFieldValue ( this . mrcParameters [ i ] . name , 'NAME' ) ;
158168 ( itemBlock as MethodMutatorArgBlock ) . originalName = this . mrcParameters [ i ] . name ;
@@ -164,11 +174,11 @@ const EVENT = {
164174 } ,
165175 mrcUpdateParams : function ( this : EventBlock ) {
166176 if ( this . mrcParameters . length > 0 ) {
167- let input = this . getInput ( 'TITLE' ) ;
177+ const input = this . getInput ( INPUT_TITLE ) ;
168178 if ( input ) {
169179 this . removeParameterFields ( input ) ;
170180 this . mrcParameters . forEach ( ( param ) => {
171- const paramName = 'PARAM_' + param . name ;
181+ const paramName = FIELD_PARAM_PREFIX + param . name ;
172182 const field = new Blockly . FieldTextInput ( param . name ) ;
173183 field . EDITABLE = false ;
174184 input . appendField ( field , paramName ) ;
@@ -178,7 +188,7 @@ const EVENT = {
178188 } ,
179189 removeParameterFields : function ( input : Blockly . Input ) {
180190 const fieldsToRemove = input . fieldRow
181- . filter ( field => field . name ?. startsWith ( 'PARAM_' ) )
191+ . filter ( field => field . name ?. startsWith ( FIELD_PARAM_PREFIX ) )
182192 . map ( field => field . name ! ) ;
183193
184194 fieldsToRemove . forEach ( fieldName => {
@@ -197,21 +207,33 @@ const EVENT = {
197207 }
198208 return legalName ;
199209 } ,
200- onBlockChanged ( block : Blockly . BlockSvg , blockEvent : Blockly . Events . BlockBase ) : void {
201- const blockBlock = block as Blockly . Block ;
202-
203- if ( blockEvent . type === Blockly . Events . BLOCK_MOVE ) {
204- const parent = ChangeFramework . getParentOfType ( block , MRC_MECHANISM_COMPONENT_HOLDER ) ;
205-
206- if ( parent ) {
207- // If it is, we allow it to stay.
208- blockBlock . setWarningText ( null ) ;
209- return ;
210+ /**
211+ * mrcOnLoad is called for each EventBlock when the blocks are loaded in the blockly workspace.
212+ */
213+ mrcOnLoad : function ( this : EventBlock ) : void {
214+ this . checkParentIsHolder ( ) ;
215+ } ,
216+ /**
217+ * mrcOnLoad is called when an EventBlock is moved.
218+ */
219+ mrcOnMove : function ( this : EventBlock ) : void {
220+ this . checkParentIsHolder ( ) ;
221+ } ,
222+ checkParentIsHolder : function ( this : EventBlock ) : void {
223+ const parentBlock = this . getParent ( ) ;
224+ if ( parentBlock && parentBlock . type === MRC_MECHANISM_COMPONENT_HOLDER ) {
225+ // If the parent block is the mechanism_component_holder, the event block is allowed to stay.
226+ // Remove any previous warning.
227+ this . setWarningText ( null , WARNING_ID_NOT_IN_HOLDER ) ;
228+ this . mrcHasWarning = false ;
229+ } else {
230+ // Otherwise, add a warning to the block.
231+ this . unplug ( true ) ;
232+ if ( ! this . mrcHasWarning ) {
233+ this . setWarningText ( Blockly . Msg . WARNING_EVENT_NOT_IN_HOLDER , WARNING_ID_NOT_IN_HOLDER ) ;
234+ this . getIcon ( Blockly . icons . IconType . WARNING ) ! . setBubbleVisible ( true ) ;
235+ this . mrcHasWarning = true ;
210236 }
211- // If we end up here it shouldn't be allowed
212- block . unplug ( true ) ;
213- blockBlock . setWarningText ( 'Events can only go in the events section of the robot or mechanism' ) ;
214- blockBlock . getIcon ( Blockly . icons . IconType . WARNING ) ! . setBubbleVisible ( true ) ;
215237 }
216238 } ,
217239 getEvent : function ( this : EventBlock ) : storageModuleContent . Event {
0 commit comments