diff --git a/docs/static/fonts/icons/iconfont.css b/docs/static/fonts/icons/iconfont.css index 67dd1f89..9f7a0a7b 100644 --- a/docs/static/fonts/icons/iconfont.css +++ b/docs/static/fonts/icons/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; - src: url("iconfont.eot?1aaa45a81be328d93991341b518bf26d?#iefix") format("embedded-opentype"), -url("iconfont.woff2?1aaa45a81be328d93991341b518bf26d") format("woff2"), -url("iconfont.woff?1aaa45a81be328d93991341b518bf26d") format("woff"); + src: url("iconfont.eot?d6ed5932401120dae3c35f6048fd5e76?#iefix") format("embedded-opentype"), +url("iconfont.woff2?d6ed5932401120dae3c35f6048fd5e76") format("woff2"), +url("iconfont.woff?d6ed5932401120dae3c35f6048fd5e76") format("woff"); } .icon { @@ -31,57 +31,51 @@ url("iconfont.woff?1aaa45a81be328d93991341b518bf26d") format("woff"); .icon-addpackage:before { content: "\f105"; } -.icon-advancedcollapsed:before { +.icon-brick:before { content: "\f106"; } -.icon-advancedexpanded:before { +.icon-controls:before { content: "\f107"; } -.icon-brick:before { +.icon-functions:before { content: "\f108"; } -.icon-controls:before { +.icon-list:before { content: "\f109"; } -.icon-functions:before { +.icon-logic:before { content: "\f10a"; } -.icon-list:before { +.icon-loops:before { content: "\f10b"; } -.icon-logic:before { +.icon-math:before { content: "\f10c"; } -.icon-loops:before { +.icon-motors:before { content: "\f10d"; } -.icon-math:before { +.icon-music:before { content: "\f10e"; } -.icon-motors:before { +.icon-sensors:before { content: "\f10f"; } -.icon-music:before { +.icon-text:before { content: "\f110"; } -.icon-sensors:before { +.icon-variables:before { content: "\f111"; } -.icon-text:before { +.icon-cancel:before { content: "\f112"; } -.icon-variables:before { +.icon-check:before { content: "\f113"; } -.icon-cancel:before { +.icon-download:before { content: "\f114"; } -.icon-check:before { +.icon-save:before { content: "\f115"; } -.icon-download:before { - content: "\f116"; -} -.icon-save:before { - content: "\f117"; -} diff --git a/docs/static/fonts/icons/iconfont.eot b/docs/static/fonts/icons/iconfont.eot index 007b931f..1de91321 100644 Binary files a/docs/static/fonts/icons/iconfont.eot and b/docs/static/fonts/icons/iconfont.eot differ diff --git a/docs/static/fonts/icons/iconfont.svg b/docs/static/fonts/icons/iconfont.svg index b3a250b6..b34c4ff4 100644 --- a/docs/static/fonts/icons/iconfont.svg +++ b/docs/static/fonts/icons/iconfont.svg @@ -22,59 +22,53 @@ - - diff --git a/docs/static/fonts/icons/iconfont.ttf b/docs/static/fonts/icons/iconfont.ttf index 115c9d92..268395b8 100644 Binary files a/docs/static/fonts/icons/iconfont.ttf and b/docs/static/fonts/icons/iconfont.ttf differ diff --git a/docs/static/fonts/icons/iconfont.woff b/docs/static/fonts/icons/iconfont.woff index e8d97e86..a61fb1dc 100644 Binary files a/docs/static/fonts/icons/iconfont.woff and b/docs/static/fonts/icons/iconfont.woff differ diff --git a/docs/static/fonts/icons/iconfont.woff2 b/docs/static/fonts/icons/iconfont.woff2 index 8d9eab3d..91b8d812 100644 Binary files a/docs/static/fonts/icons/iconfont.woff2 and b/docs/static/fonts/icons/iconfont.woff2 differ diff --git a/libs/core/_locales/core-jsdoc-strings.json b/libs/core/_locales/core-jsdoc-strings.json index 309c14dd..faf6ef75 100644 --- a/libs/core/_locales/core-jsdoc-strings.json +++ b/libs/core/_locales/core-jsdoc-strings.json @@ -21,8 +21,6 @@ "brick.Button.pauseUntil": "Waits until the event is raised", "brick.Button.pauseUntil|param|ev": "the event to wait for", "brick.Button.wasPressed": "See if the button was pressed again since the last time you checked.", - "brick._imagePicker": "An image", - "brick._imagePicker|param|image": "the image", "brick.buttonDown": "Down button on the EV3 Brick.", "brick.buttonEnter": "Enter button on the EV3 Brick.", "brick.buttonLeft": "Left button on the EV3 Brick.", diff --git a/libs/core/_locales/core-strings.json b/libs/core/_locales/core-strings.json index a68e8a47..6e94110a 100644 --- a/libs/core/_locales/core-strings.json +++ b/libs/core/_locales/core-strings.json @@ -28,7 +28,6 @@ "brick.Button.onEvent|block": "on %button|%event", "brick.Button.pauseUntil|block": "pause until %button|%event", "brick.Button.wasPressed|block": "%button|was pressed", - "brick._imagePicker|block": "%image", "brick.buttonDown|block": "down", "brick.buttonEnter|block": "enter", "brick.buttonLeft|block": "left", diff --git a/libs/core/images.ts b/libs/core/images.ts index 5e4d482b..999cb197 100644 --- a/libs/core/images.ts +++ b/libs/core/images.ts @@ -1,260 +1,260 @@ namespace images { - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsBigSmile = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsHeartLarge = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsHeartSmall = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsMouth1open = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsMouth1shut = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsMouth2open = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsMouth2shut = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsSad = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsSick = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsSmile = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsSwearing = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsTalking = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsWink = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const expressionsZzz = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesAngry = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesAwake = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesBlackEye = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesBottomLeft = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesBottomRight = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesCrazy1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesCrazy2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesDisappointed = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesDizzy = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesDown = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesEvil = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesHurt = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesKnockedOut = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesLove = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesMiddleLeft = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesMiddleRight = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesNeutral = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesNuclear = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesPinchLeft = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesPinchMiddle = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesPinchRight = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesSleeping = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesTear = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesTiredLeft = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesTiredMiddle = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesTiredRight = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesToxic = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesUp = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const eyesWinking = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationAccept = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationBackward = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationDecline = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationForward = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationLeft = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationNoGo = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationQuestionMark = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationRight = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationStop1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationStop2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationThumbsDown = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationThumbsUp = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const informationWarning = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoColorSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoEv3icon = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoEv3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoGyroSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoIrBeacon = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoIrSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoLego = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoLargeMotor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoMindstorms = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoMediumMotor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoSoundSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoTempSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoTouchSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const legoUsSensor = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsBomb = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsBoom = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsFire = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsFlowers = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsForest = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsLightOff = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsLightOn = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsLightning = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsNight = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsPirate = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsSnow = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const objectsTarget = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressBar0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressBar1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressBar2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressBar3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressBar4 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDial0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDial1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDial2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDial3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDial4 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDots0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDots1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDots2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressDots3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressHourglass0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressHourglass1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressHourglass2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressTimer0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressTimer1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressTimer2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressTimer3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressTimer4 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressWaterLevel0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressWaterLevel1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressWaterLevel2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const progressWaterLevel3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemAccept1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemAccept2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemAlert = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemBox = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemBusy0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemBusy1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemDecline1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemDecline2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemDotEmpty = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemDotFull = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemEv3small = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemPlay = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider0 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider1 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider2 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider3 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider4 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider5 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider6 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider7 = screen.unpackPNG(hex``); - //% fixedInstance jres blockIdentity=brick._imagePicker + //% fixedInstance jres blockIdentity=brick.__imagePicker export const systemSlider8 = screen.unpackPNG(hex``); } diff --git a/libs/core/screen.ts b/libs/core/screen.ts index 9027f5f7..f70dc6e1 100644 --- a/libs/core/screen.ts +++ b/libs/core/screen.ts @@ -158,7 +158,7 @@ namespace brick { //% image.fieldOptions.columns=6 //% image.fieldOptions.hasSearchBar=true //% group="Screen" weight=0 blockHidden=1 - export function _imagePicker(image: Image): Image { + export function __imagePicker(image: Image): Image { return image; } diff --git a/libs/ev3/ns.ts b/libs/ev3/ns.ts index 8db92e47..32690ba1 100644 --- a/libs/ev3/ns.ts +++ b/libs/ev3/ns.ts @@ -1,19 +1,19 @@ -//% color="#68C3E2" weight=100 icon="\uf108" +//% color="#68C3E2" weight=100 icon="\uf106" //% groups='["Buttons", "Screen"]' //% labelLineWidth=0 namespace brick { } -//% color="#C8509B" weight=95 icon="\uf111" +//% color="#C8509B" weight=95 icon="\uf10f" //% labelLineWidth=0 //% groups='["Ultrasonic Sensor", "Touch Sensor", "Color Sensor", "Infrared Sensor", "Remote Infrared Beacon", "Gyro Sensor"]' //% groupIcons='["\uf101","\uf103","\uf102","","","\uf104"]' namespace sensors { } -//% color="#A5CA18" weight=90 icon="\uf10f" +//% color="#A5CA18" weight=90 icon="\uf10d" //% groups='["Motion", "Sensors", "Chassis"]' //% labelLineWidth=0 namespace motors { diff --git a/package.json b/package.json index 1d418da4..4e6cecef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pxt-ev3", - "version": "0.0.45", + "version": "0.0.46", "description": "LEGO Mindstorms EV3 for Microsoft MakeCode", "private": true, "keywords": [ diff --git a/pxtarget.json b/pxtarget.json index ac5ef3f1..64a763e7 100644 --- a/pxtarget.json +++ b/pxtarget.json @@ -139,9 +139,7 @@ }, "monacoColors": { "editor.background": "#ecf6ff" - }, - "simAnimationEnter": "horizontal flip in", - "simAnimationExit": "horizontal flip out" + } }, "ignoreDocsErrors": true } diff --git a/sim/dalboard.ts b/sim/dalboard.ts index 9ad960b8..4e613d9e 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -23,20 +23,6 @@ namespace pxsim { } export class EV3Board extends CoreBoard { - // state & update logic for component services - // neopixelState: CommonNeoPixelState; - buttonState: EV3ButtonState; - slideSwitchState: SlideSwitchState; - lightSensorState: AnalogSensorState; - thermometerState: AnalogSensorState; - thermometerUnitState: number; - microphoneState: AnalogSensorState; - edgeConnectorState: EdgeConnectorState; - capacitiveSensorState: CapacitiveSensorState; - accelerometerState: AccelerometerState; - touchButtonState: TouchButtonState; - irState: InfraredState; - view: SVGSVGElement; outputState: EV3OutputState; @@ -86,11 +72,6 @@ namespace pxsim { // TODO break; } - case "irpacket": { - let ev = msg; - this.irState.receive(new RefBuffer(ev.packet)); - break; - } } } @@ -120,6 +101,9 @@ namespace pxsim { document.body.innerHTML = ""; // clear children document.body.appendChild(this.view = viewHost.getView() as SVGSVGElement); + this.inputNodes = []; + this.outputNodes = []; + return Promise.resolve(); } @@ -127,26 +111,33 @@ namespace pxsim { return svg.toDataUri(new XMLSerializer().serializeToString(this.view)); } - //defaultNeopixelPin() { - // return this.edgeConnectorState.getPin(CPlayPinName.D8); - //} - - getDefaultPitchPin() { - return this.edgeConnectorState.getPin(CPlayPinName.D6); - } - getBrickNode() { return this.brickNode; } + motorUsed(port:number, large: boolean) { + for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) { + const p = 1 << i; + if (port & p) { + const motorPort = this.motorMap[p]; + if (!this.outputNodes[motorPort]) + this.outputNodes[motorPort] = new MotorNode(motorPort, large); + } + } + } + getMotor(port: number, large?: boolean): MotorNode[] { - if (port == 0xFF) return this.getMotors(); // Return all motors - const motorPort = this.motorMap[port]; - if (this.outputNodes[motorPort] == undefined) { - this.outputNodes[motorPort] = large ? - new LargeMotorNode(motorPort) : new MediumMotorNode(motorPort); + const r = []; + for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) { + const p = 1 << i; + if (port & p) { + const motorPort = this.motorMap[p]; + const outputNode = this.outputNodes[motorPort]; + if (outputNode) + r.push(outputNode); + } } - return [this.outputNodes[motorPort]]; + return r; } getMotors() { @@ -154,7 +145,7 @@ namespace pxsim { } getSensor(port: number, type: number): SensorNode { - if (this.inputNodes[port] == undefined) { + if (!this.inputNodes[port]) { switch (type) { case DAL.DEVICE_TYPE_GYRO: this.inputNodes[port] = new GyroSensorNode(port); break; case DAL.DEVICE_TYPE_COLOR: this.inputNodes[port] = new ColorSensorNode(port); break; diff --git a/sim/state/color.ts b/sim/state/color.ts index 42ce61df..ad5f7ac5 100644 --- a/sim/state/color.ts +++ b/sim/state/color.ts @@ -20,7 +20,7 @@ namespace pxsim { export class ColorSensorNode extends UartSensorNode { id = NodeType.ColorSensor; - private color: number; + private color: number = 50; constructor(port: number) { super(port); @@ -31,10 +31,8 @@ namespace pxsim { } setColor(color: number) { - if (this.color != color) { - this.color = color; - this.setChangedState(); - } + this.color = color; + this.setChangedState(); } getValue() { diff --git a/sim/state/control.ts b/sim/state/control.ts index dabc588b..0735c5b3 100644 --- a/sim/state/control.ts +++ b/sim/state/control.ts @@ -81,7 +81,6 @@ namespace pxsim.control { export function dmesg(s: string) { console.log("DMESG: " + s) } - } namespace pxsim.output { diff --git a/sim/state/input.ts b/sim/state/input.ts index a00ceec2..1f62e95e 100644 --- a/sim/state/input.ts +++ b/sim/state/input.ts @@ -2,8 +2,8 @@ namespace pxsim.motors { export function __motorUsed(port: number, large: boolean) { - console.log("MOTOR INIT " + port); - const motors = ev3board().getMotor(port, large); + //console.log("MOTOR INIT " + port); + ev3board().motorUsed(port, large); runtime.queueDisplayUpdate(); } } @@ -11,7 +11,7 @@ namespace pxsim.motors { namespace pxsim.sensors { export function __sensorUsed(port: number, type: number) { - console.log("SENSOR INIT " + port + ", type: " + type); + //console.log("SENSOR INIT " + port + ", type: " + type); const sensor = ev3board().getSensor(port, type); runtime.queueDisplayUpdate(); } diff --git a/sim/state/motor.ts b/sim/state/motor.ts index fffab968..308d2197 100644 --- a/sim/state/motor.ts +++ b/sim/state/motor.ts @@ -19,8 +19,8 @@ namespace pxsim { // console.log("motor before read"); for (let port = 0; port < DAL.NUM_OUTPUTS; ++port) { const output = outputs[port]; - const speed = output ? Math.round(outputs[port].getSpeed()) : 0; - const angle = output ? Math.round(outputs[port].getAngle()) : 0; + const speed = output ? outputs[port].getSpeed() : 0; + const angle = output ? outputs[port].getAngle() : 0; const tci = MotorDataOff.TachoCounts + port * MotorDataOff.Size; const tsi = MotorDataOff.TachoSensor + port * MotorDataOff.Size; data[tci] = data[tci + 1] = data[tci + 2] = data[tci + 3] = 0; // Tacho count diff --git a/sim/state/motornode.ts b/sim/state/motornode.ts new file mode 100644 index 00000000..5676eae6 --- /dev/null +++ b/sim/state/motornode.ts @@ -0,0 +1,141 @@ +namespace pxsim { + + export class MotorNode extends BaseNode { + isOutput = true; + private rotationsPerMilliSecond: number; + + // current state + private angle: number = 0; + private tacho: number = 0; + private speed: number = 0; + private polarity: number = 1; // -1, 1 or -1 + + private started: boolean; + private speedCmd: DAL; + private speedCmdValues: number[]; + private speedCmdTacho: number; + private speedCmdTime: number; + + constructor(port: number, large: boolean) { + super(port); + this.setLarge(large); + } + + getSpeed() { + return this.speed * (this.polarity == 0 ? -1 : 1); + } + + getAngle() { + return this.angle; + } + + setSpeedCmd(cmd: DAL, values: number[]) { + this.speedCmd = cmd; + this.speedCmdValues = values; + this.speedCmdTacho = this.angle; + this.speedCmdTime = pxsim.U.now(); + } + + clearSpeedCmd() { + delete this.speedCmd; + } + + setLarge(large: boolean) { + this.id = large ? NodeType.LargeMotor : NodeType.MediumMotor; + // large 170 rpm (https://education.lego.com/en-us/products/ev3-large-servo-motor/45502) + this.rotationsPerMilliSecond = (large ? 170 : 250) / 60000; + } + + setPolarity(polarity: number) { + // Either 1 or 255 (reverse) + /* + -1 : Motor will run backward + 0 : Motor will run opposite direction + 1 : Motor will run forward + */ + this.polarity = polarity; + } + + reset() { + // not sure what reset does... + } + + clearCount() { + this.tacho = 0; + this.angle = 0; + } + + stop() { + this.started = false; + } + + start() { + this.started = true; + } + + updateState(elapsed: number) { + console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`) + const interval = Math.min(20, elapsed); + let t = 0; + while(t < elapsed) { + let dt = interval; + if (t + dt > elapsed) dt = elapsed - t; + this.updateStateStep(dt); + t += dt; + } + } + + private updateStateStep(elapsed: number) { + // compute new speed + switch (this.speedCmd) { + case DAL.opOutputSpeed: + case DAL.opOutputPower: + // assume power == speed + // TODO: PID + this.speed = this.speedCmdValues[0]; + break; + case DAL.opOutputTimeSpeed: + case DAL.opOutputTimePower: + case DAL.opOutputStepPower: + case DAL.opOutputStepSpeed: + // ramp up, run, ramp down, using time + const speed = this.speedCmdValues[0]; + const step1 = this.speedCmdValues[1]; + const step2 = this.speedCmdValues[2]; + const step3 = this.speedCmdValues[3]; + const brake = this.speedCmdValues[4]; + const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed) + ? pxsim.U.now() - this.speedCmdTime + : this.tacho - this.speedCmdTacho; + if (dstep < step1) // rampup + this.speed = speed * dstep / step1; + else if (dstep < step1 + step2) // run + this.speed = speed; + else if (dstep < step1 + step2 + step3) + this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3); + else { + if (brake) this.speed = 0; + this.clearSpeedCmd(); + } + this.speed = Math.round(this.speed); // integer only + break; + } + + // compute delta angle + const rotations = this.getSpeed() / 100 * this.rotationsPerMilliSecond * elapsed; + const deltaAngle = Math.round(rotations * 360); + if (deltaAngle) { + this.angle += deltaAngle; + this.tacho += Math.abs(deltaAngle); + this.setChangedState(); + } + + // if the motor was stopped or there are no speed commands, + // let it coast to speed 0 + if (this.speed && !(this.started || this.speedCmd)) { + // decay speed 5% per tick + this.speed = Math.round(Math.max(0, Math.abs(this.speed) - 10) * Math.sign(this.speed)); + } + } + } +} \ No newline at end of file diff --git a/sim/state/motors.ts b/sim/state/motors.ts deleted file mode 100644 index fcf71bcd..00000000 --- a/sim/state/motors.ts +++ /dev/null @@ -1,87 +0,0 @@ -namespace pxsim { - - export abstract class MotorNode extends BaseNode { - isOutput = true; - - protected angle: number = 0; - - private rotationsPerMilliSecond: number; - private speed: number; - private large: boolean; - private rotation: number; - private polarity: boolean; - - constructor(port: number, rpm: number) { - super(port); - this.rotationsPerMilliSecond = rpm / 60000; - } - - setSpeed(speed: number) { - if (this.speed != speed) { - this.speed = speed; - this.changed = true; - this.setChangedState(); - } - } - - setLarge(large: boolean) { - this.large = large; - } - - getSpeed() { - return this.speed; - } - - stepSpeed(speed: number, angle: number, brake: boolean) { - // TODO: implement - } - - setPolarity(polarity: number) { - // Either 1 or 255 (reverse) - this.polarity = polarity === 255; - // TODO: implement - } - - reset() { - // TODO: implement - } - - stop() { - // TODO: implement - } - - start() { - // TODO: implement - this.setChangedState(); - } - - public getAngle() { - return this.angle; - } - - updateState(elapsed: number) { - const rotations = this.getSpeed() / 100 * this.rotationsPerMilliSecond * elapsed; - const angle = rotations * 360; - if (angle) { - this.angle += angle; - this.setChangedState(); - } - } - } - - export class MediumMotorNode extends MotorNode { - id = NodeType.MediumMotor; - - constructor(port: number) { - super(port, 250); - } - } - - export class LargeMotorNode extends MotorNode { - id = NodeType.LargeMotor; - - constructor(port: number) { - super(port, 170); - } - } -} \ No newline at end of file diff --git a/sim/state/output.ts b/sim/state/output.ts index a3b93f8e..53d9f420 100644 --- a/sim/state/output.ts +++ b/sim/state/output.ts @@ -34,24 +34,32 @@ namespace pxsim { motors.forEach(motor => motor.reset()); return 2; } - case DAL.opOutputStepSpeed: { + case DAL.opOutputClearCount: + const port = buf.data[1]; + const motors = ev3board().getMotor(port); + motors.forEach(motor => motor.clearCount()); + break; + case DAL.opOutputStepPower: + case DAL.opOutputStepSpeed: + case DAL.opOutputTimePower: + case DAL.opOutputTimeSpeed: { // step speed const port = buf.data[1]; - const speed = buf.data[2] << 24 >> 24; // signed byte - // note that b[3] is padding - const step1 = buf.data[4]; - const step2 = buf.data[5]; // angle - const step3 = buf.data[6]; - const brake = buf.data[7]; + const speed = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); // signed byte + // note that b[3] is padding + const step1 = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 4); + const step2 = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 8); + const step3 = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 12); + const brake = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 16); //console.log(buf); const motors = ev3board().getMotor(port); - motors.forEach(motor => motor.stepSpeed(speed, step2, brake === 1)); + motors.forEach(motor => motor.setSpeedCmd(cmd, [speed, step1, step2, step3, brake])); return 2; } case DAL.opOutputStop: { // stop const port = buf.data[1]; - const brake = buf.data[2]; + const brake = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); const motors = ev3board().getMotor(port); motors.forEach(motor => motor.stop()); return 2; @@ -59,9 +67,9 @@ namespace pxsim { case DAL.opOutputSpeed: { // setSpeed const port = buf.data[1]; - const speed = buf.data[2] << 24 >> 24; // signed byte + const speed = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); const motors = ev3board().getMotor(port); - motors.forEach(motor => motor.setSpeed(speed)); + motors.forEach(motor => motor.setSpeedCmd(cmd, [speed])); return 2; } case DAL.opOutputStart: { @@ -74,7 +82,7 @@ namespace pxsim { case DAL.opOutputPolarity: { // reverse const port = buf.data[1]; - const polarity = buf.data[2]; + const polarity = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); const motors = ev3board().getMotor(port); motors.forEach(motor => motor.setPolarity(polarity)); return 2; @@ -86,6 +94,9 @@ namespace pxsim { motors.forEach(motor => motor.setLarge(large)); return 2; } + default: + console.warn('unknown cmd: ' + cmd); + break; } console.log("pwm write"); diff --git a/sim/state/uart.ts b/sim/state/uart.ts index eeabdb9d..cc612b03 100644 --- a/sim/state/uart.ts +++ b/sim/state/uart.ts @@ -88,7 +88,7 @@ namespace pxsim { const inputNodes = ev3board().getInputNodes(); for (let port = 0; port < DAL.NUM_INPUTS; port++) { const node = inputNodes[port]; - if (node) { + if (node && node.isUart()) { // Actual const index = 0; //UartOff.Actual + port * 2; util.map16Bit(data, UartOff.Raw + DAL.MAX_DEVICE_DATALENGTH * 300 * port + DAL.MAX_DEVICE_DATALENGTH * index, Math.floor(node.getValue())) diff --git a/sim/visuals/assets/Large Motor.svg b/sim/visuals/assets/Large Motor.svg index 68a857fe..b25fe9ae 100644 --- a/sim/visuals/assets/Large Motor.svg +++ b/sim/visuals/assets/Large Motor.svg @@ -1,69 +1,87 @@ - + - - + + - - + + - - + + Large Motor - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + diff --git a/sim/visuals/assets/LargeMotorsvg.ts b/sim/visuals/assets/LargeMotorsvg.ts index 3e791aac..c38ce09b 100644 --- a/sim/visuals/assets/LargeMotorsvg.ts +++ b/sim/visuals/assets/LargeMotorsvg.ts @@ -1,70 +1,88 @@ namespace pxsim { - export const LARGE_MOTOR_SVG = ` + export const LARGE_MOTOR_SVG = ` - - + + - - + + - - + + Large Motor - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + diff --git a/sim/visuals/assets/MediumMotor.svg b/sim/visuals/assets/MediumMotor.svg index be04262c..0bc67d30 100644 --- a/sim/visuals/assets/MediumMotor.svg +++ b/sim/visuals/assets/MediumMotor.svg @@ -1,28 +1,33 @@ - + - - - + + + MediumMotor - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - diff --git a/sim/visuals/assets/MediumMotorsvg.ts b/sim/visuals/assets/MediumMotorsvg.ts index 32354672..33054802 100644 --- a/sim/visuals/assets/MediumMotorsvg.ts +++ b/sim/visuals/assets/MediumMotorsvg.ts @@ -1,30 +1,35 @@ namespace pxsim.visuals { - export const MEDIUM_MOTOR_SVG = ` + export const MEDIUM_MOTOR_SVG = ` - - - + + + - Medium Motor - - - - - - - + MediumMotor + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - `; } \ No newline at end of file diff --git a/sim/visuals/board.ts b/sim/visuals/board.ts index e6239b92..35b426c0 100644 --- a/sim/visuals/board.ts +++ b/sim/visuals/board.ts @@ -33,13 +33,21 @@ namespace pxsim.visuals { font-family:"Lucida Console", Monaco, monospace; font-size:8px; fill:#fff; - pointer-events: none; user-select: none; + pointer-events: none; + user-select: none; } .sim-text.small { font-size:6px; } + .sim-text.large { + font-size:30px; + } + .sim-text.number { + font-family: Courier, Lato, Work Sans, PT Serif, Source Serif Pro; + font-weight: bold; + } .sim-text.inverted { - fill:#000; + fill:#5A5A5A; } /* Color Grid */ @@ -99,16 +107,9 @@ namespace pxsim.visuals { private layoutView: LayoutView; - private controlGroup: ViewContainer; - private selectedNode: NodeType; - private selectedPort: number; - private controlView: View; private cachedControlNodes: { [index: string]: View[] } = {}; private cachedDisplayViews: { [index: string]: LayoutElement[] } = {}; - private closeGroup: ViewContainer; - private closeIconView: View; - private screenCanvas: HTMLCanvasElement; private screenCanvasCtx: CanvasRenderingContext2D; private screenCanvasData: ImageData; @@ -143,7 +144,12 @@ namespace pxsim.visuals { Runtime.messagePosted = (msg) => { switch (msg.type || "") { - case "status": if ((msg as pxsim.SimulatorStateMessage).state == "killed") this.kill(); break; + case "status": { + const state = (msg as pxsim.SimulatorStateMessage).state; + if (state == "killed") this.kill(); + if (state == "running") this.begin(); + break; + } } } } @@ -177,36 +183,6 @@ namespace pxsim.visuals { this.layoutView.updateTheme(theme); } - public resize() { - const bounds = this.element.getBoundingClientRect(); - this.width = bounds.width; - this.height = bounds.height; - this.layoutView.layout(bounds.width, bounds.height); - - if (this.selectedNode) { - const scale = this.width / this.closeIconView.getInnerWidth() / 10; - // Translate close icon - this.closeIconView.scale(Math.max(0, Math.min(1, scale))); - const closeIconWidth = this.closeIconView.getWidth(); - const closeIconHeight = this.closeIconView.getHeight(); - const closeCoords = this.layoutView.getCloseIconCoords(closeIconWidth, closeIconHeight); - this.closeIconView.translate(closeCoords.x, closeCoords.y); - } - - if (this.controlView) { - const h = this.controlView.getInnerHeight(); - const w = this.controlView.getInnerWidth(); - const bh = this.layoutView.getModuleBounds().height - this.closeIconView.getHeight(); - const bw = this.layoutView.getModuleBounds().width - (this.width * MODULE_INNER_PADDING_RATIO * 2); - this.controlView.scale(Math.min(bh / h, bw / w), false); - - const controlCoords = this.layoutView.getSelectedCoords(); - this.controlView.translate(controlCoords.x, controlCoords.y); - } - - //this.updateScreen(); - } - private getControlForNode(id: NodeType, port: number) { if (this.cachedControlNodes[id] && this.cachedControlNodes[id][port]) { return this.cachedControlNodes[id][port]; @@ -237,9 +213,10 @@ namespace pxsim.visuals { } case NodeType.MediumMotor: case NodeType.LargeMotor: { - // const state = ev3board().getMotor(port)[0]; - // view = new MotorInputControl(this.element, this.defs, state, port); - // break; + // TODO: figure out if the motor is in "input" or "output" mode + const state = ev3board().getMotors()[port]; + view = new MotorReporterControl(this.element, this.defs, state, port); + break; } } @@ -285,6 +262,10 @@ namespace pxsim.visuals { return undefined; } + private getCloseIconView() { + return new CloseIconControl(this.element, this.defs, new PortNode(-1), -1); + } + private buildDom() { this.wrapper = document.createElement('div'); this.wrapper.style.display = 'inline'; @@ -299,25 +280,10 @@ namespace pxsim.visuals { this.layoutView = new LayoutView(); this.layoutView.inject(this.element); - this.controlGroup = new ViewContainer(); - this.controlGroup.inject(this.element); - - this.closeGroup = new ViewContainer(); - this.closeGroup.inject(this.element); - // Add EV3 module element this.layoutView.setBrick(new BrickView(-1)); - this.closeIconView = new CloseIconControl(this.element, this.defs, new PortNode(-1), -1); - this.closeIconView.registerClick(() => { - this.layoutView.clearSelected(); - this.updateState(); - }) - this.closeGroup.addView(this.closeIconView); - this.closeIconView.setVisible(false); - this.resize(); - //this.updateState(); // Add Screen canvas to board this.buildScreenCanvas(); @@ -331,6 +297,17 @@ namespace pxsim.visuals { }); } + public resize() { + if (!this.element) return; + this.width = document.body.offsetWidth; + this.height = document.body.offsetHeight; + this.layoutView.layout(this.width, this.height); + + this.updateState(); + let state = ev3board().screenState; + this.updateScreenStep(state); + } + private buildScreenCanvas() { this.screenCanvas = document.createElement("canvas"); this.screenCanvas.id = "board-screen-canvas"; @@ -357,21 +334,60 @@ namespace pxsim.visuals { } private kill() { - if (this.lastAnimationId) cancelAnimationFrame(this.lastAnimationId); + this.running = false; + if (this.lastAnimationIds.length > 0) { + this.lastAnimationIds.forEach(animationId => { + cancelAnimationFrame(animationId); + }) + } + // Save previous inputs for the next cycle + EV3View.previousSelectedInputs = ev3board().getInputNodes().map((node, index) => (this.getDisplayViewForNode(node.id, index).getSelected()) ? node.id : -1) + EV3View.previousSeletedOutputs = ev3board().getMotors().map((node, index) => (this.getDisplayViewForNode(node.id, index).getSelected()) ? node.id : -1); } - private lastAnimationId: number; + private static previousSelectedInputs: number[]; + private static previousSeletedOutputs: number[]; + + private static isPreviousInputSelected(index: number, id: number) { + if (EV3View.previousSelectedInputs && EV3View.previousSelectedInputs[index] == id) { + EV3View.previousSelectedInputs[index] = undefined; + return true; + } + return false; + } + + private static isPreviousOutputSelected(index: number, id: number) { + if (EV3View.previousSeletedOutputs && EV3View.previousSeletedOutputs[index] == id) { + EV3View.previousSeletedOutputs[index] = undefined; + return true; + } + return false; + } + + private begin() { + this.running = true; + this.updateState(); + } + + private running: boolean = false; + private lastAnimationIds: number[] = []; public updateState() { - if (this.lastAnimationId) cancelAnimationFrame(this.lastAnimationId); + if (this.lastAnimationIds.length > 0) { + this.lastAnimationIds.forEach(animationId => { + cancelAnimationFrame(animationId); + }) + } + if (!this.running) return; const fps = GAME_LOOP_FPS; let now; - let then = Date.now(); + let then = pxsim.U.now(); let interval = 1000 / fps; let delta; let that = this; function loop() { - that.lastAnimationId = requestAnimationFrame(loop); - now = Date.now(); + const animationId = requestAnimationFrame(loop); + that.lastAnimationIds.push(animationId); + now = pxsim.U.now(); delta = now - then; if (delta > interval) { then = now; @@ -382,67 +398,50 @@ namespace pxsim.visuals { } private updateStateStep(elapsed: number) { - const selected = this.layoutView.getSelected(); - let selectedChanged = false; const inputNodes = ev3board().getInputNodes(); inputNodes.forEach((node, index) => { node.updateState(elapsed); - if (!node.didChange()) return; const view = this.getDisplayViewForNode(node.id, index); + if (!node.didChange() && !view.didChange()) return; if (view) { - this.layoutView.setInput(index, view); + const isSelected = EV3View.isPreviousInputSelected(index, node.id) || view.getSelected(); + if (isSelected && !view.getSelected()) view.setSelected(true); + const control = isSelected ? this.getControlForNode(node.id, index) : undefined; + const closeIcon = control ? this.getCloseIconView() : undefined; + this.layoutView.setInput(index, view, control, closeIcon); view.updateState(); - if (selected == view) selectedChanged = true; + if (control) control.updateState(); } }); const brickNode = ev3board().getBrickNode(); if (brickNode.didChange()) { - this.getDisplayViewForNode(ev3board().getBrickNode().id, -1).updateState(); + this.getDisplayViewForNode(brickNode.id, -1).updateState(); } const outputNodes = ev3board().getMotors(); outputNodes.forEach((node, index) => { node.updateState(elapsed); - if (!node.didChange()) return; const view = this.getDisplayViewForNode(node.id, index); + if (!node.didChange() && !view.didChange()) return; if (view) { - this.layoutView.setOutput(index, view); + const isSelected = EV3View.isPreviousOutputSelected(index, node.id) || view.getSelected(); + if (isSelected && !view.getSelected()) view.setSelected(true); + const control = isSelected ? this.getControlForNode(node.id, index) : undefined; + const closeIcon = control ? this.getCloseIconView() : undefined; + this.layoutView.setOutput(index, view, control, closeIcon); view.updateState(); - if (selected == view) selectedChanged = true; + if (control) control.updateState(); } }); - if (selected && (selected.getId() !== this.selectedNode || selected.getPort() !== this.selectedPort)) { - this.selectedNode = selected.getId(); - this.selectedPort = selected.getPort(); - this.controlGroup.clear(); - const control = this.getControlForNode(this.selectedNode, selected.getPort()); - if (control) { - this.controlView = control; - this.controlGroup.addView(control); - } - this.closeIconView.setVisible(true); - this.resize(); - } else if (!selected) { - this.controlGroup.clear(); - this.controlView = undefined; - this.selectedNode = undefined; - this.selectedPort = undefined; - this.closeIconView.setVisible(false); + let state = ev3board().screenState; + if (state.didChange()) { + this.updateScreenStep(state); } - - if (selectedChanged && selected) { - this.controlView.updateState(); - } - - this.updateScreenStep(); } - private updateScreenStep() { - let state = ev3board().screenState; - if (!state.didChange()) return; - + private updateScreenStep(state: EV3ScreenState) { const bBox = this.layoutView.getBrick().getScreenBBox(); if (!bBox || bBox.width == 0) return; diff --git a/sim/visuals/controlView.ts b/sim/visuals/controlView.ts index 6d0fa6a9..4bcebe92 100644 --- a/sim/visuals/controlView.ts +++ b/sim/visuals/controlView.ts @@ -1,17 +1,20 @@ -/// +/// namespace pxsim.visuals { export const CONTROL_WIDTH = 87.5; export const CONTROL_HEIGHT = 175; + export const CONTROL_TEXT_COLOR = '#000'; + export abstract class ControlView extends SimView implements LayoutElement { - private background: SVGSVGElement; + protected content: SVGSVGElement; abstract getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement): SVGElement; constructor(protected parent: SVGSVGElement, protected globalDefs: SVGDefsElement, protected state: T, protected port: number) { super(state); + } getInnerWidth(): number { @@ -34,18 +37,39 @@ namespace pxsim.visuals { return false; } - buildDom(width: number): SVGElement { - this.background = svg.elt("svg", { height: "100%", width: "100%"}) as SVGSVGElement; - this.background.appendChild(this.getInnerView(this.parent, this.globalDefs)); - return this.background; + buildDom(): SVGElement { + this.content = svg.elt("svg", { viewBox: `0 0 ${this.getInnerWidth()} ${this.getInnerHeight()}` }) as SVGSVGElement; + this.content.appendChild(this.getInnerView(this.parent, this.globalDefs)); + return this.content; + } + + public resize(width: number, height: number) { + super.resize(width, height); + this.updateDimensions(width, height); + } + + private updateDimensions(width: number, height: number) { + if (this.content) { + const currentWidth = this.getInnerWidth(); + const currentHeight = this.getInnerHeight(); + const newHeight = currentHeight / currentWidth * width; + const newWidth = currentWidth / currentHeight * height; + if (newHeight > height) { + // scale width instead + this.content.setAttribute('width', `${newWidth}`); + this.content.setAttribute('height', `${height}`); + // translate to the middle (width) + this.translate(width / 2 - newWidth / 2, 0); + } else { + this.content.setAttribute('width', `${width}`); + this.content.setAttribute('height', `${newHeight}`); + // translate to the middle (height) + this.translate(0, height / 2 - newHeight / 2); + } + } } onComponentVisible() { - - } - - getWeight() { - return 0; } } } \ No newline at end of file diff --git a/sim/visuals/controls/closeIcon.ts b/sim/visuals/controls/closeIcon.ts index 02b5307f..f16b1650 100644 --- a/sim/visuals/controls/closeIcon.ts +++ b/sim/visuals/controls/closeIcon.ts @@ -17,6 +17,17 @@ namespace pxsim.visuals { return this.closeGroup; } + + buildDom(): SVGElement { + this.content = svg.elt("svg", { width: "100%", height: "100%"}) as SVGSVGElement; + this.content.appendChild(this.getInnerView()); + return this.content; + } + + public resize(width: number, height: number) { + super.resize(width, height); + } + public getInnerHeight() { return 32; } diff --git a/sim/visuals/controls/colorGrid.ts b/sim/visuals/controls/colorGrid.ts index f8faca73..df89cfc8 100644 --- a/sim/visuals/controls/colorGrid.ts +++ b/sim/visuals/controls/colorGrid.ts @@ -7,35 +7,44 @@ namespace pxsim.visuals { getInnerView() { this.group = svg.elt("g") as SVGGElement; - this.group.setAttribute("transform", `translate(17, ${35 + this.getHeight() / 4}) scale(5)`) + this.group.setAttribute("transform", `translate(1.02, 1.5) scale(0.8)`) - const colorIds = ['red', 'yellow', 'blue', 'green', 'black', 'grey']; - const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#000', '#6c2d00']; + const colorIds = ['red', 'yellow', 'blue', 'green', undefined, 'grey']; + const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', undefined, '#6c2d00']; const colorValue = [5, 4, 2, 3, 1, 7]; let cy = -4; for (let c = 0; c < colorIds.length; c++) { const cx = c % 2 == 0 ? 2.2 : 8.2; if (c % 2 == 0) cy += 5; - const circle = pxsim.svg.child(this.group, "circle", { 'class': 'sim-color-grid-circle', 'cx': cx, 'cy': cy, 'r': '2', 'style': `fill: ${colors[c]}` }); - circle.addEventListener(pointerEvents.down, ev => { - this.setColor(colorValue[c]); - }) + if (colorIds[c]) { + const circle = pxsim.svg.child(this.group, "circle", { 'class': 'sim-color-grid-circle', 'cx': cx, 'cy': cy, 'r': '2', 'style': `fill: ${colors[c]}` }); + circle.addEventListener(pointerEvents.down, ev => { + this.setColor(colorValue[c]); + }) + } } const whiteCircleWrapper = pxsim.svg.child(this.group, "g", { 'id': 'white-cirlce-wrapper' }); - pxsim.svg.child(whiteCircleWrapper, "circle", { 'class': 'sim-color-grid-circle', 'cx': 2.2, 'cy': '16', 'r': '2', 'style': `fill: #fff` }); - pxsim.svg.child(whiteCircleWrapper, "circle", { 'cx': 2.2, 'cy': '16', 'r': '2', 'style': `fill: none;stroke: #94989b;stroke-width: 0.5px` }); + pxsim.svg.child(whiteCircleWrapper, "circle", { 'class': 'sim-color-grid-circle', 'cx': 2.2, 'cy': '11', 'r': '2', 'style': `fill: #fff` }); + pxsim.svg.child(whiteCircleWrapper, "circle", { 'cx': 2.2, 'cy': '11', 'r': '2', 'style': `fill: none;stroke: #94989b;stroke-width: 0.1px` }); whiteCircleWrapper.addEventListener(pointerEvents.down, ev => { this.setColor(6); }) return this.group; } + getInnerWidth() { + return 10.2; + } + + getInnerHeight() { + return 15; + } + private setColor(color: number) { const state = this.state; state.setColor(color); } } - } \ No newline at end of file diff --git a/sim/visuals/controls/colorWheel.ts b/sim/visuals/controls/colorWheel.ts index 80ff4ccf..f0ff4adb 100644 --- a/sim/visuals/controls/colorWheel.ts +++ b/sim/visuals/controls/colorWheel.ts @@ -10,7 +10,7 @@ namespace pxsim.visuals { getInnerView(parent: SVGSVGElement) { this.defs = svg.child(this.element, "defs", {}); this.group = svg.elt("g") as SVGGElement; - this.group.setAttribute("transform", `translate(12, ${this.getHeight() / 2 - 15}) scale(2.5)`) + this.group.setAttribute("transform", `translate(12, 0) scale(2)`) let gc = "gradient-color"; this.colorGradient = svg.linearGradient(this.defs, gc, true); @@ -53,6 +53,14 @@ namespace pxsim.visuals { return this.group; } + getInnerWidth() { + return CONTROL_WIDTH; + } + + getInnerHeight() { + return CONTROL_WIDTH; + } + updateState() { if (!this.visible) { return; diff --git a/sim/visuals/controls/distanceSlider.ts b/sim/visuals/controls/distanceSlider.ts index 033b8b0d..a4542ab6 100644 --- a/sim/visuals/controls/distanceSlider.ts +++ b/sim/visuals/controls/distanceSlider.ts @@ -7,33 +7,38 @@ namespace pxsim.visuals { private gradient: SVGLinearGradientElement; private slider: SVGGElement; - private static SLIDER_HANDLE_HEIGHT = 31; + private reporter: SVGTextElement; - private isVisible = false; + private static SLIDER_HANDLE_HEIGHT = 26; + private static SLIDER_SIDE_PADDING = 6; getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { let gid = "gradient-slider-" + this.getId(); this.group = svg.elt("g") as SVGGElement; this.gradient = createGradient(gid, this.getGradientDefinition()); - this.gradient.setAttribute('x1', '-438.37'); - this.gradient.setAttribute('y1', '419.43'); - this.gradient.setAttribute('x2', '-438.37'); - this.gradient.setAttribute('y2', '418.43'); - this.gradient.setAttribute('gradientTransform', 'matrix(50, 0, 0, -110, 21949.45, 46137.67)'); - this.gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); + this.gradient.setAttribute('x1', '0%'); + this.gradient.setAttribute('y1', '0%'); + this.gradient.setAttribute('x2', '0%'); + this.gradient.setAttribute('y2', '100%'); + // this.gradient.setAttribute('gradientTransform', 'matrix(50, 0, 0, -110, 21949.45, 46137.67)'); + // this.gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); globalDefs.appendChild(this.gradient); this.group = svg.elt("g") as SVGGElement; - const sliderGroup = pxsim.svg.child(this.group, "g"); - sliderGroup.setAttribute("transform", `translate(0, ${10 + this.getTopPadding()})`) + const reporterGroup = pxsim.svg.child(this.group, "g"); + reporterGroup.setAttribute("transform", `translate(31, 42)`); + this.reporter = pxsim.svg.child(reporterGroup, "text", { 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement; - const rect = pxsim.svg.child(sliderGroup, "rect", { 'x': this.getLeftPadding(), 'y': 2, 'width': this.getWidth() - this.getLeftPadding() * 2, 'height': this.getContentHeight(), 'style': `fill: url(#${gid})` }); + const sliderGroup = pxsim.svg.child(this.group, "g"); + sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`) + + const rect = pxsim.svg.child(sliderGroup, "rect", { 'x': DistanceSliderControl.SLIDER_SIDE_PADDING, 'y': 2, 'width': this.getSliderWidth() - DistanceSliderControl.SLIDER_SIDE_PADDING * 2, 'height': this.getSliderHeight(), 'style': `fill: url(#${gid})` }); this.slider = pxsim.svg.child(sliderGroup, "g", { "transform": "translate(0,0)" }) as SVGGElement; const sliderInner = pxsim.svg.child(this.slider, "g"); - pxsim.svg.child(sliderInner, "rect", { 'width': this.getWidth(), 'height': DistanceSliderControl.SLIDER_HANDLE_HEIGHT, 'rx': '2', 'ry': '2', 'style': 'fill: #f12a21' }); - pxsim.svg.child(sliderInner, "rect", { 'x': '0.5', 'y': '0.5', 'width': this.getWidth() - 1, 'height': DistanceSliderControl.SLIDER_HANDLE_HEIGHT - 1, 'rx': '1.5', 'ry': '1.5', 'style': 'fill: none;stroke: #b32e29' }); + pxsim.svg.child(sliderInner, "rect", { 'width': this.getSliderWidth(), 'height': DistanceSliderControl.SLIDER_HANDLE_HEIGHT, 'rx': '2', 'ry': '2', 'style': 'fill: #f12a21' }); + pxsim.svg.child(sliderInner, "rect", { 'x': '0.5', 'y': '0.5', 'width': this.getSliderWidth() - 1, 'height': DistanceSliderControl.SLIDER_HANDLE_HEIGHT - 1, 'rx': '1.5', 'ry': '1.5', 'style': 'fill: none;stroke: #b32e29' }); const dragSurface = svg.child(this.group, "rect", { x: 0, @@ -66,41 +71,43 @@ namespace pxsim.visuals { return this.group; } - private getLeftPadding() { - return this.getInnerWidth() * 0.12; + getInnerHeight() { + return 192; } - private getTopPadding() { - return this.getInnerHeight() / 4; + getInnerWidth() { + return 111; } - private getContentHeight() { - return this.getInnerHeight() * 0.6; + private getReporterHeight() { + return 50; + } + + private getSliderHeight() { + return 110; + } + + private getSliderWidth() { + return 62; } updateState() { - if (!this.isVisible) { + if (!this.visible) { return; } const node = this.state; const percentage = node.getValue() / 10; /* convert back to cm */ - const y = this.getContentHeight() * percentage / this.getMax(); + const y = this.getSliderHeight() * percentage / this.getMax(); this.slider.setAttribute("transform", `translate(0, ${y - DistanceSliderControl.SLIDER_HANDLE_HEIGHT / 2})`); - } - - onComponentVisible() { - super.onComponentVisible(); - this.isVisible = true; - } - - onComponentHidden() { - this.isVisible = false; + // Update reporter text + this.reporter.textContent = `${parseFloat((percentage).toString()).toFixed(0)}`; } private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { let cur = svg.cursorPoint(pt, parent, ev); - const height = this.getContentHeight(); //DistanceSliderControl.SLIDER_HEIGHT; - let t = Math.max(0, Math.min(1, (this.getTopPadding() + height + this.top / this.scaleFactor - cur.y / this.scaleFactor) / height)) + const height = this.getSliderHeight(); + const bBox = this.content.getBoundingClientRect(); + let t = Math.max(0, Math.min(1, (DistanceSliderControl.SLIDER_HANDLE_HEIGHT + height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height)) const state = this.state; state.setDistance((1 - t) * (this.getMax())); @@ -118,7 +125,7 @@ namespace pxsim.visuals { return { stops: [ { offset: 0, color: '#626262' }, - { offset: 1, color: "#ddd" } + { offset: 100, color: "#ddd" } ] }; } diff --git a/sim/visuals/controls/motorReporter.ts b/sim/visuals/controls/motorReporter.ts new file mode 100644 index 00000000..e8a2a048 --- /dev/null +++ b/sim/visuals/controls/motorReporter.ts @@ -0,0 +1,60 @@ + + +namespace pxsim.visuals { + + export class MotorReporterControl extends ControlView { + private group: SVGGElement; + + private circleBar: SVGCircleElement; + + private reporter: SVGTextElement; + + getInnerView() { + this.group = svg.elt("g") as SVGGElement; + + const outerCircle = pxsim.svg.child(this.group, "circle", { + 'stroke-dasharray': '565.48', 'stroke-dashoffset': '0', + 'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent; transition: stroke-dashoffset 1s linear;`, + 'stroke': '#a8aaa8', 'stroke-width': '1rem' }) as SVGCircleElement; + this.circleBar = pxsim.svg.child(this.group, "circle", { + 'stroke-dasharray': '565.48', 'stroke-dashoffset': '0', + 'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent; transition: stroke-dashoffset 1s linear;`, + 'stroke': '#f12a21', 'stroke-width': '1rem' }) as SVGCircleElement; + + this.reporter = pxsim.svg.child(this.group, "text", { + 'x': this.getWidth() / 2, 'y': this.getHeight() / 2, + 'text-anchor': 'middle', 'alignment-baseline': 'middle', + 'style': 'font-size: 50px', + 'class': 'sim-text inverted number' }) as SVGTextElement; + + return this.group; + } + + getInnerWidth() { + return 200; + } + + getInnerHeight() { + return 200; + } + + updateState() { + if (!this.visible) { + return; + } + const node = this.state; + const speed = node.getSpeed(); + this.updateSpeed(speed); + + // Update reporter + this.reporter.textContent = `${speed}`; + } + + private updateSpeed(speed: number) { + let c = Math.PI * (90 * 2); + speed = Math.abs(speed); + let pct = ((100 - speed) / 100) * c; + this.circleBar.setAttribute('stroke-dashoffset', `${pct}`); + } + } +} \ No newline at end of file diff --git a/sim/visuals/controls/rotationSlider.ts b/sim/visuals/controls/rotationSlider.ts index df5d8117..1a557857 100644 --- a/sim/visuals/controls/rotationSlider.ts +++ b/sim/visuals/controls/rotationSlider.ts @@ -6,8 +6,6 @@ namespace pxsim.visuals { private group: SVGGElement; private slider: SVGGElement; - private isVisible = false; - private static SLIDER_WIDTH = 70; private static SLIDER_HEIGHT = 78; @@ -61,7 +59,7 @@ namespace pxsim.visuals { } updateState() { - if (!this.isVisible) { + if (!this.visible) { return; } const node = this.state; @@ -71,19 +69,11 @@ namespace pxsim.visuals { this.slider.setAttribute("transform", `translate(${x}, ${y})`); } - onComponentVisible() { - super.onComponentVisible(); - this.isVisible = true; - } - - onComponentHidden() { - this.isVisible = false; - } - private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { let cur = svg.cursorPoint(pt, parent, ev); const width = CONTROL_WIDTH; //DistanceSliderControl.SLIDER_HEIGHT; - let t = Math.max(0, Math.min(1, (width + this.left / this.scaleFactor - cur.x / this.scaleFactor) / width)) + const bBox = this.content.getBoundingClientRect(); + let t = Math.max(0, Math.min(1, (width + bBox.left / this.scaleFactor - cur.x / this.scaleFactor) / width)) const state = this.state; state.setAngle((1 - t) * (100)); diff --git a/sim/visuals/layoutView.ts b/sim/visuals/layoutView.ts index a72cc5f0..b2184303 100644 --- a/sim/visuals/layoutView.ts +++ b/sim/visuals/layoutView.ts @@ -1,67 +1,56 @@ /// -/// +/// /// namespace pxsim.visuals { - export const DEFAULT_WIDTH = 350; - export const DEFAULT_HEIGHT = 700; - export const BRICK_HEIGHT_RATIO = 1 / 3; export const MODULE_AND_WIRING_HEIGHT_RATIO = 1 / 3; // For inputs and outputs - export const MODULE_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO * 3 / 4; - export const WIRING_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO / 4; + export const MODULE_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO * 4 / 5; + export const WIRING_HEIGHT_RATIO = MODULE_AND_WIRING_HEIGHT_RATIO / 5; export const MODULE_INNER_PADDING_RATIO = 1 / 35; + export const MAX_MODULE_WIDTH = 100; + export interface LayoutElement extends View { getId(): number; getPort(): number; getPaddingRatio(): number; getWiringRatio(): number; - setSelected(selected: boolean): void; } export class LayoutView extends ViewContainer { private inputs: LayoutElement[] = []; private outputs: LayoutElement[] = []; + private inputContainers: ViewContainer[] = []; + private outputContainers: ViewContainer[] = []; + + private inputControls: View[] = []; + private outputControls: View[] = []; + + private inputCloseIcons: View[] = []; + private outputCloseIcons: View[] = []; + private inputWires: WireView[] = []; private outputWires: WireView[] = []; - private selected: number; - private selectedIsInput: boolean; private brick: BrickView; private offsets: number[]; private contentGroup: SVGGElement; private scrollGroup: SVGGElement; private renderedViews: Map = {}; - - private childScaleFactor: number; - - private totalLength: number; - private height: number; private hasDimensions = false; constructor() { super(); - this.outputs = [ - new PortView(0, 'A'), - new PortView(1, 'B'), - new PortView(2, 'C'), - new PortView(3, 'D') - ]; + this.outputContainers = [new ViewContainer(), new ViewContainer, new ViewContainer(), new ViewContainer()]; + this.inputContainers = [new ViewContainer(), new ViewContainer, new ViewContainer(), new ViewContainer()]; this.brick = new BrickView(0); - this.inputs = [ - new PortView(0, '1'), - new PortView(1, '2'), - new PortView(2, '3'), - new PortView(3, '4') - ]; - for (let port = 0; port < DAL.NUM_OUTPUTS; port++) { this.outputWires[port] = new WireView(port); } @@ -72,8 +61,7 @@ namespace pxsim.visuals { public layout(width: number, height: number) { this.hasDimensions = true; - this.width = width; - this.height = height; + this.resize(width, height); this.scrollGroup.setAttribute("width", width.toString()); this.scrollGroup.setAttribute("height", height.toString()); this.position(); @@ -81,6 +69,7 @@ namespace pxsim.visuals { public setBrick(brick: BrickView) { this.brick = brick; + this.brick.inject(this.scrollGroup); this.position(); } @@ -88,55 +77,112 @@ namespace pxsim.visuals { return this.brick; } - public setInput(port: number, child: LayoutElement) { - if (this.inputs[port]) { - // Remove current input - this.inputs[port].dispose(); + public setInput(port: number, view: LayoutElement, control?: View, closeIcon?: View) { + if (this.inputs[port] != view || this.inputControls[port] != control) { + if (this.inputs[port]) { + // Remove current input + this.inputs[port].dispose(); + } + this.inputs[port] = view; + if (this.inputControls[port]) { + this.inputControls[port].dispose(); + } + this.inputControls[port] = control; + this.inputCloseIcons[port] = closeIcon; + + this.inputContainers[port].clear(); + this.inputContainers[port].addView(view); + + if (control) this.inputContainers[port].addView(control); + + if (view.hasClick()) view.registerClick((ev: any) => { + view.setSelected(true); + runtime.queueDisplayUpdate(); + }, true); + + if (control && closeIcon) { + this.inputContainers[port].addView(closeIcon); + closeIcon.registerClick(() => { + // Clear selection + view.setSelected(false); + runtime.queueDisplayUpdate(); + }) + } } - this.inputs[port] = child; + this.position(); } - public setOutput(port: number, child: LayoutElement) { - if (this.outputs[port]) { - // Remove current input - this.outputs[port].dispose(); + public setOutput(port: number, view: LayoutElement, control?: View, closeIcon?: View) { + if (this.outputs[port] != view || this.outputControls[port] != control) { + if (this.outputs[port]) { + // Remove current output + this.outputs[port].dispose(); + } + this.outputs[port] = view; + if (this.outputControls[port]) { + this.outputControls[port].dispose(); + } + this.outputControls[port] = control; + this.outputCloseIcons[port] = closeIcon; + + this.outputContainers[port].clear(); + this.outputContainers[port].addView(view); + + if (control) this.outputContainers[port].addView(control); + + if (view.hasClick()) view.registerClick((ev: any) => { + view.setSelected(true); + runtime.queueDisplayUpdate(); + }, true) + + if (control && closeIcon) { + this.outputContainers[port].addView(closeIcon); + closeIcon.registerClick(() => { + // Clear selection + view.setSelected(false); + runtime.queueDisplayUpdate(); + }) + } } - this.outputs[port] = child; + this.position(); } - public onClick(index: number, input: boolean, ev: any) { - this.setSelected(index, input); - } - - public clearSelected() { - this.selected = undefined; - this.selectedIsInput = undefined; - } - - public setSelected(index: number, input?: boolean) { - if (index !== this.selected || input !== this.selectedIsInput) { - this.selected = index; - this.selectedIsInput = input; - const node = this.getSelected(); - if (node) node.setSelected(true); - - //this.redoPositioning(); - runtime.queueDisplayUpdate(); - } - } - - public getSelected() { - if (this.selected !== undefined) { - return this.selectedIsInput ? this.inputs[this.selected] : this.outputs[this.selected]; - } - return undefined; - } - - protected buildDom(width: number) { + protected buildDom() { this.contentGroup = svg.elt("g") as SVGGElement; this.scrollGroup = svg.child(this.contentGroup, "g") as SVGGElement; + + this.inputs = []; + this.outputs = []; + this.inputControls = []; + this.outputControls = []; + + // Inject all wires + for (let port = 0; port < DAL.NUM_OUTPUTS; port++) { + this.outputWires[port].inject(this.scrollGroup); + } + for (let port = 0; port < DAL.NUM_INPUTS; port++) { + this.inputWires[port].inject(this.scrollGroup); + } + + // Inject all view containers + for (let i = 0; i < 4; i++) { + this.inputContainers[i].inject(this.scrollGroup); + this.outputContainers[i].inject(this.scrollGroup); + } + + // Inject all ports + this.setInput(0, new PortView(0, 'A')); + this.setInput(1, new PortView(1, 'B')); + this.setInput(2, new PortView(2, 'C')); + this.setInput(3, new PortView(3, 'D')); + + this.setOutput(0, new PortView(0, '1')); + this.setOutput(1, new PortView(1, '2')); + this.setOutput(2, new PortView(2, '3')); + this.setOutput(3, new PortView(3, '4')); + return this.contentGroup; } @@ -171,34 +217,63 @@ namespace pxsim.visuals { this.offsets = []; - const selectedNode = this.getSelected(); + const contentWidth = this.width; + if (!contentWidth) return; + const contentHeight = this.height; + if (!contentHeight) return; - const contentWidth = this.width || DEFAULT_WIDTH; - const contentHeight = this.height || DEFAULT_HEIGHT; + const noConnections = this.outputs.concat(this.inputs).filter(m => m.getId() != NodeType.Port).length == 0; + + if (noConnections) { + // No connections render the entire board + this.brick.resize(contentWidth, contentHeight); + this.brick.translate(0, 0); + + // Hide all other connections + this.outputs.concat(this.inputs).forEach(m => m.setVisible(false)); + return; + } else { + this.outputs.concat(this.inputs).forEach(m => m.setVisible(true)); + } const moduleHeight = this.getModuleHeight(); const brickHeight = this.getBrickHeight(); - this.brick.inject(this.scrollGroup); const brickWidth = this.brick.getInnerWidth() / this.brick.getInnerHeight() * brickHeight; const brickPadding = (contentWidth - brickWidth) / 2; - const modulePadding = contentWidth / 35; + const modulePadding = this.getModulePadding(); const moduleSpacing = contentWidth / 4; - const moduleWidth = moduleSpacing - (modulePadding * 2); - let currentX = modulePadding; + const moduleWidth = this.getInnerModuleWidth(); + let currentX = this.getModulePadding(); let currentY = 0; this.outputs.forEach((n, i) => { - const outputPadding = moduleWidth * n.getPaddingRatio(); - const outputWidth = moduleWidth - outputPadding * 2; - n.inject(this.scrollGroup, outputWidth); - n.resize(outputWidth); - const nHeight = n.getHeight() / n.getWidth() * outputWidth; - n.translate(currentX + outputPadding, currentY + moduleHeight - nHeight); - n.setSelected(n == selectedNode); - if (n.hasClick()) n.registerClick((ev: any) => { - this.onClick(i, false, ev); - }) + this.outputContainers[i].translate(currentX, currentY); + if (this.outputs[i]) { + const view = this.outputs[i]; + const outputPadding = this.getInnerModuleWidth() * view.getPaddingRatio(); + const desiredOutputWidth = this.getInnerModuleWidth() - outputPadding * 2; + const outputWidth = Math.min(desiredOutputWidth, MAX_MODULE_WIDTH); + const outputHeight = this.getModuleHeight(); + + // Translate and resize view + view.resize(outputWidth, outputHeight); + const viewHeight = view.getInnerHeight() / view.getInnerWidth() * outputWidth; + view.translate(outputPadding + ((desiredOutputWidth - outputWidth) / 2), outputHeight - viewHeight, true); + + // Resize control + const control = this.outputControls[i]; + if (control) { + control.resize(this.getInnerModuleWidth(), outputHeight); + + // Translate close icon + const closeIcon = this.outputCloseIcons[i]; + if (closeIcon) { + const closeIconWidth = closeIcon.getWidth(); + closeIcon.translate(this.getInnerModuleWidth() / 2 - closeIconWidth / 2, 0); + } + } + } currentX += moduleSpacing; }) @@ -206,7 +281,7 @@ namespace pxsim.visuals { currentY = moduleHeight; const wireBrickSpacing = brickWidth / 5; - const wiringYPadding = 10; + const wiringYPadding = 5; let wireStartX = 0; let wireEndX = brickPadding + wireBrickSpacing; let wireEndY = currentY + this.getWiringHeight() + wiringYPadding; @@ -214,7 +289,6 @@ namespace pxsim.visuals { // Draw output lines for (let port = 0; port < DAL.NUM_OUTPUTS; port++) { - if (!this.outputWires[port].isRendered()) this.outputWires[port].inject(this.scrollGroup); this.outputWires[port].updateDimensions(wireStartX + moduleSpacing * this.outputs[port].getWiringRatio(), wireStartY, wireEndX, wireEndY); this.outputWires[port].setSelected(this.outputs[port].getId() == NodeType.Port); wireStartX += moduleSpacing; @@ -225,22 +299,39 @@ namespace pxsim.visuals { currentY += this.getWiringHeight(); // Render the brick in the middle - this.brick.resize(brickWidth); + this.brick.resize(brickWidth, brickHeight); this.brick.translate(currentX, currentY); currentX = modulePadding; currentY += brickHeight + this.getWiringHeight(); this.inputs.forEach((n, i) => { - const inputPadding = moduleWidth * n.getPaddingRatio(); - const inputWidth = moduleWidth - inputPadding * 2; - n.inject(this.scrollGroup, inputWidth); - n.resize(inputWidth); - n.translate(currentX + inputPadding, currentY); - n.setSelected(n == selectedNode); - if (n.hasClick()) n.registerClick((ev: any) => { - this.onClick(i, true, ev); - }) + this.inputContainers[i].translate(currentX, currentY); + if (this.inputs[i]) { + const view = this.inputs[i]; + const inputPadding = this.getInnerModuleWidth() * view.getPaddingRatio(); + const desiredInputWidth = this.getInnerModuleWidth() - inputPadding * 2; + const inputWidth = Math.min(desiredInputWidth, MAX_MODULE_WIDTH); + const inputHeight = this.getModuleHeight(); + + // Translate and resize view + view.resize(inputWidth, inputHeight); + view.translate(inputPadding + ((desiredInputWidth - inputWidth) / 2), 0, true); + + // Resize control + const control = this.inputControls[i]; + if (control) { + control.resize(this.getInnerModuleWidth(), inputHeight); + + // Translate and resize close icon + const closeIcon = this.inputCloseIcons[i]; + if (closeIcon) { + const closeIconWidth = closeIcon.getWidth(); + const closeIconHeight = closeIcon.getHeight(); + closeIcon.translate(this.getInnerModuleWidth() / 2 - closeIconWidth / 2, this.getModuleHeight() - closeIconHeight); + } + } + } currentX += moduleSpacing; }) @@ -251,7 +342,6 @@ namespace pxsim.visuals { // Draw input lines for (let port = 0; port < DAL.NUM_INPUTS; port++) { - if (!this.inputWires[port].isRendered()) this.inputWires[port].inject(this.scrollGroup); this.inputWires[port].updateDimensions(wireStartX, wireStartY, wireEndX, wireEndY); this.inputWires[port].setSelected(this.inputs[port].getId() == NodeType.Port); wireStartX += moduleSpacing; @@ -259,33 +349,12 @@ namespace pxsim.visuals { } } - public getSelectedCoords() { - const selected = this.getSelected(); - if (!selected) return undefined; - const port = this.getSelected().getPort(); - return { - x: this.getSelected().getPort() * this.width / 4 + this.width * MODULE_INNER_PADDING_RATIO, - y: this.selectedIsInput ? this.getModuleHeight() + 2 * this.getWiringHeight() + this.getBrickHeight() : this.getModuleHeight() / 4 - } - } - - public getCloseIconCoords(closeIconWidth: number, closeIconHeight: number) { - return { - x: this.getSelected().getPort() * this.width / 4 + this.getModuleBounds().width / 2 - closeIconWidth / 2, - y: this.selectedIsInput ? this.getModuleHeight() + 2 * this.getWiringHeight() + this.getBrickHeight() + this.getModuleHeight() - closeIconHeight : 0 - } - } - - public getModuleHeight() { - return (this.height || DEFAULT_HEIGHT) * MODULE_HEIGHT_RATIO; - } - public getBrickHeight() { - return (this.height || DEFAULT_HEIGHT) * BRICK_HEIGHT_RATIO; + return this.height * BRICK_HEIGHT_RATIO; } public getWiringHeight() { - return (this.height || DEFAULT_HEIGHT) * WIRING_HEIGHT_RATIO; + return this.height * WIRING_HEIGHT_RATIO; } public getModuleBounds() { @@ -294,5 +363,17 @@ namespace pxsim.visuals { height: this.getModuleHeight() } } + + public getModulePadding() { + return this.getModuleBounds().width / 35; + } + + public getInnerModuleWidth() { + return this.getModuleBounds().width - (this.getModulePadding() * 2); + } + + public getModuleHeight() { + return this.height * MODULE_HEIGHT_RATIO; + } } } \ No newline at end of file diff --git a/sim/visuals/nodes/brickView.ts b/sim/visuals/nodes/brickView.ts index 8619f053..6a732713 100644 --- a/sim/visuals/nodes/brickView.ts +++ b/sim/visuals/nodes/brickView.ts @@ -1,8 +1,8 @@ -/// +/// namespace pxsim.visuals { - export class BrickView extends StaticModuleView implements LayoutElement { + export class BrickView extends ModuleView implements LayoutElement { private static EV3_SCREEN_ID = "ev3_screen"; private static EV3_LIGHT_ID = "btn_color"; @@ -41,9 +41,9 @@ namespace pxsim.visuals { public updateThemeCore() { let theme = this.theme; - svg.fill(this.buttons[0], theme.buttonUps[0]); - svg.fill(this.buttons[1], theme.buttonUps[1]); - svg.fill(this.buttons[2], theme.buttonUps[2]); + // svg.fill(this.buttons[0], theme.buttonUps[0]); + // svg.fill(this.buttons[1], theme.buttonUps[1]); + // svg.fill(this.buttons[2], theme.buttonUps[2]); } private lastLightPattern: number = -1; diff --git a/sim/visuals/nodes/colorSensorView.ts b/sim/visuals/nodes/colorSensorView.ts index 19ee2ed7..5637af6d 100644 --- a/sim/visuals/nodes/colorSensorView.ts +++ b/sim/visuals/nodes/colorSensorView.ts @@ -1,7 +1,7 @@ -/// +/// namespace pxsim.visuals { - export class ColorSensorView extends StaticModuleView implements LayoutElement { + export class ColorSensorView extends ModuleView implements LayoutElement { private control: ColorGridControl; @@ -10,10 +10,11 @@ namespace pxsim.visuals { } public getPaddingRatio() { - return 1 / 8; + return 1 / 4; } public updateState() { + super.updateState(); // TODO: show different color modes } } diff --git a/sim/visuals/nodes/gyroSensorView.ts b/sim/visuals/nodes/gyroSensorView.ts index c8280f8d..37850463 100644 --- a/sim/visuals/nodes/gyroSensorView.ts +++ b/sim/visuals/nodes/gyroSensorView.ts @@ -1,14 +1,14 @@ -/// +/// namespace pxsim.visuals { - export class GyroSensorView extends StaticModuleView implements LayoutElement { + export class GyroSensorView extends ModuleView implements LayoutElement { constructor(port: number) { super(GYRO_SVG, "gyro", NodeType.GyroSensor, port); } public getPaddingRatio() { - return 1 / 4; + return 0.3; } } } \ No newline at end of file diff --git a/sim/visuals/nodes/largeMotorView.ts b/sim/visuals/nodes/largeMotorView.ts index 42aeb2e8..0e10e685 100644 --- a/sim/visuals/nodes/largeMotorView.ts +++ b/sim/visuals/nodes/largeMotorView.ts @@ -1,15 +1,16 @@ -/// +/// namespace pxsim.visuals { - export class LargeMotorView extends StaticModuleView implements LayoutElement { + export class LargeMotorView extends ModuleView implements LayoutElement { - private static ROTATING_ECLIPSE_ID = "1eb2ae58-2419-47d4-86bf-4f26a7f0cf61"; + private static ROTATING_ECLIPSE_ID = "hole"; constructor(port: number) { super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port); } updateState() { + super.updateState(); const motorState = ev3board().getMotors()[this.port]; if (!motorState) return; const speed = motorState.getSpeed(); @@ -20,14 +21,14 @@ namespace pxsim.visuals { private setMotorAngle(angle: number) { const holeEl = this.content.getElementById(this.normalizeId(LargeMotorView.ROTATING_ECLIPSE_ID)) - const width = 34; - const height = 34; + const width = 125.92; + const height = 37.9; const transform = `rotate(${angle} ${width / 2} ${height / 2})`; holeEl.setAttribute("transform", transform); } getWiringRatio() { - return 0.62; + return 0.37; } } } \ No newline at end of file diff --git a/sim/visuals/nodes/mediumMotorView.ts b/sim/visuals/nodes/mediumMotorView.ts index 546b7e4a..d980f95e 100644 --- a/sim/visuals/nodes/mediumMotorView.ts +++ b/sim/visuals/nodes/mediumMotorView.ts @@ -1,12 +1,12 @@ -/// +/// namespace pxsim.visuals { export const MOTOR_ROTATION_FPS = 32; - export class MediumMotorView extends StaticModuleView implements LayoutElement { + export class MediumMotorView extends ModuleView implements LayoutElement { - private static ROTATING_ECLIPSE_ID = "Hole"; + private static ROTATING_ECLIPSE_ID = "medmotor_Hole"; private hasPreviousAngle: boolean; private previousAngle: number; @@ -16,10 +16,11 @@ namespace pxsim.visuals { } public getPaddingRatio() { - return 1 / 10; + return 1 / 5; } updateState() { + super.updateState(); const motorState = ev3board().getMotors()[this.port]; if (!motorState) return; const speed = motorState.getSpeed(); @@ -30,9 +31,9 @@ namespace pxsim.visuals { private setMotorAngle(angle: number) { const holeEl = this.content.getElementById(this.normalizeId(MediumMotorView.ROTATING_ECLIPSE_ID)) - const width = 47.9; - const height = 47.2; - const transform = `translate(-1.5 -1.49) rotate(${angle} ${width / 2} ${height / 2})`; + const width = 44.45; + const height = 44.45; + const transform = `translate(2 1.84) rotate(${angle} ${width / 2} ${height / 2})`; holeEl.setAttribute("transform", transform); } } diff --git a/sim/visuals/nodes/staticView.ts b/sim/visuals/nodes/moduleView.ts similarity index 77% rename from sim/visuals/nodes/staticView.ts rename to sim/visuals/nodes/moduleView.ts index 1527c433..ede63633 100644 --- a/sim/visuals/nodes/staticView.ts +++ b/sim/visuals/nodes/moduleView.ts @@ -1,10 +1,11 @@ namespace pxsim.visuals { - export class StaticModuleView extends View implements LayoutElement { + export class ModuleView extends View implements LayoutElement { protected content: SVGSVGElement; protected controlShown: boolean; - protected selected: boolean; + + protected opacity: number; constructor(protected xml: string, protected prefix: string, protected id: NodeType, protected port: NodeType) { super(); @@ -45,9 +46,8 @@ namespace pxsim.visuals { return 0.5; } - protected buildDom(width: number): SVGElement { + protected buildDom(): SVGElement { this.content = svg.parseString(this.xml); - this.updateDimensions(width); this.buildDomCore(); this.attachEvents(); if (this.hasClick()) @@ -82,15 +82,17 @@ namespace pxsim.visuals { public attachEvents() { } - public resize(width: number) { - this.updateDimensions(width); + public resize(width: number, height: number) { + super.resize(width, height); + this.updateDimensions(width, height); } - private updateDimensions(width: number) { + private updateDimensions(width: number, height: number) { if (this.content) { const currentWidth = this.getInnerWidth(); const currentHeight = this.getInnerHeight(); const newHeight = currentHeight / currentWidth * width; + const newWidth = currentWidth / currentHeight * height; this.content.setAttribute('width', `${width}`); this.content.setAttribute('height', `${newHeight}`); } @@ -101,23 +103,28 @@ namespace pxsim.visuals { } public setSelected(selected: boolean) { - this.selected = selected; + super.setSelected(selected); + this.updateOpacity(); + } + + public updateState() { this.updateOpacity(); } protected updateOpacity() { if (this.rendered) { - const opacity = this.selected ? "0.5" : "1"; - if (this.hasClick()) { - this.setOpacity(opacity); + const opacity = this.selected ? 0.2 : 1; + if (this.hasClick() && this.opacity != opacity) { + this.opacity = opacity; + this.setOpacity(this.opacity); if (this.selected) this.content.style.cursor = ""; else this.content.style.cursor = "pointer"; } } } - protected setOpacity(opacity: string) { - this.element.setAttribute("opacity", opacity); + protected setOpacity(opacity: number) { + this.element.setAttribute("opacity", `${opacity}`); } } } \ No newline at end of file diff --git a/sim/visuals/nodes/portView.ts b/sim/visuals/nodes/portView.ts index 5399713f..53908c75 100644 --- a/sim/visuals/nodes/portView.ts +++ b/sim/visuals/nodes/portView.ts @@ -1,8 +1,8 @@ -/// +/// namespace pxsim.visuals { - export class PortView extends StaticModuleView implements LayoutElement { + export class PortView extends ModuleView implements LayoutElement { constructor(port: NodeType, private label: string) { super(PORT_SVG, "port", NodeType.Port, port); diff --git a/sim/visuals/nodes/touchSensorView.ts b/sim/visuals/nodes/touchSensorView.ts index 0c623218..78932bdf 100644 --- a/sim/visuals/nodes/touchSensorView.ts +++ b/sim/visuals/nodes/touchSensorView.ts @@ -1,7 +1,7 @@ -/// +/// namespace pxsim.visuals { - export class TouchSensorView extends StaticModuleView implements LayoutElement { + export class TouchSensorView extends ModuleView implements LayoutElement { private static RECT_ID = ["touch_gradient4", "touch_gradient3", "touch_gradient2", "touch_gradient1"]; private static TOUCH_GRADIENT_UNPRESSED = ["linear-gradient-2", "linear-gradient-3", "linear-gradient-4", "linear-gradient-5"]; @@ -17,7 +17,7 @@ namespace pxsim.visuals { } public getPaddingRatio() { - return 1 / 10; + return 1 / 4; } public hasClick() { diff --git a/sim/visuals/nodes/ultrasonicView.ts b/sim/visuals/nodes/ultrasonicView.ts index d370bd35..5ae9565b 100644 --- a/sim/visuals/nodes/ultrasonicView.ts +++ b/sim/visuals/nodes/ultrasonicView.ts @@ -1,7 +1,7 @@ -/// +/// namespace pxsim.visuals { - export class UltrasonicSensorView extends StaticModuleView implements LayoutElement { + export class UltrasonicSensorView extends ModuleView implements LayoutElement { constructor(port: number) { super(ULTRASONIC_SVG, "ultrasonic", NodeType.UltrasonicSensor, port); diff --git a/sim/visuals/view.ts b/sim/visuals/view.ts index 029f139d..b0d7ae9f 100644 --- a/sim/visuals/view.ts +++ b/sim/visuals/view.ts @@ -3,14 +3,19 @@ namespace pxsim.visuals { protected element: SVGGElement; protected rendered = false; protected visible = false; + protected selected: boolean; protected width: number = 0; + protected height: number = 0; protected left: number = 0; protected top: number = 0; protected scaleFactor: number = 1; + private changed: boolean; + private hover: boolean = false; + protected theme: IBoardTheme; - protected abstract buildDom(width: number): SVGElement; + protected abstract buildDom(): SVGElement; public abstract getInnerWidth(): number; public abstract getInnerHeight(): number; @@ -83,9 +88,26 @@ namespace pxsim.visuals { } private onClickHandler: (ev: any) => void; - public registerClick(handler: (ev: any) => void) { + public registerClick(handler: (ev: any) => void, zoom?: boolean) { this.onClickHandler = handler; - this.getView().addEventListener(pointerEvents.up, this.onClickHandler); + if (zoom) { + this.getView().addEventListener(pointerEvents.up, (ev: any) => { + if (!this.getSelected()) { + this.onClickHandler(ev); + this.setHover(false); + } + }); + this.getView().addEventListener(pointerEvents.move, () => { + if (!this.getSelected()) { + this.setHover(true); + } + }); + this.getView().addEventListener(pointerEvents.leave, () => { + this.setHover(false); + }); + } else { + this.getView().addEventListener(pointerEvents.up, this.onClickHandler); + } } public dispose() { @@ -98,7 +120,7 @@ namespace pxsim.visuals { this.element = svg.elt("g") as SVGGElement; View.track(this); - const content = this.buildDom(this.width); + const content = this.buildDom(); if (content) { this.element.appendChild(content); } @@ -108,16 +130,30 @@ namespace pxsim.visuals { return this.element; } - public resize(width: number) { + public resize(width: number, height: number) { this.width = width; + this.height = height; } private updateTransform() { if (this.rendered) { - let transform = `translate(${this.left} ${this.top})`; + let left = this.left; + let top = this.top; + let scaleFactor = this.scaleFactor; + if (this.hover) { + const hoverScaleFactor = scaleFactor + 0.05; + // Scale around center of module + const centerX = this.getWidth() / 2; + const centerY = this.getHeight() / 2; + left = left - centerX * (hoverScaleFactor - 1); + top = top - centerY * (hoverScaleFactor - 1); + scaleFactor = hoverScaleFactor; + } - if (this.scaleFactor !== 1) { - transform += ` scale(${this.scaleFactor})`; + let transform = `translate(${left} ${top})`; + + if (scaleFactor !== 1) { + transform += ` scale(${scaleFactor})`; } this.element.setAttribute("transform", transform); @@ -149,6 +185,40 @@ namespace pxsim.visuals { delete View.allViews[id]; } } + + ///////// HOVERED STATE ///////////// + + public getHover() { + return this.hover; + } + + public setHover(hover: boolean) { + if (this.hover != hover) { + this.hover = hover; + this.updateTransform(); + } + } + + ///////// SELECTED STATE ///////////// + + public getSelected() { + return this.selected; + } + + public setSelected(selected: boolean) { + this.selected = selected; + this.setChangedState(); + } + + protected setChangedState() { + this.changed = true; + } + + public didChange() { + const res = this.changed; + this.changed = false; + return res; + } } export abstract class SimView extends View implements LayoutElement { @@ -222,7 +292,7 @@ namespace pxsim.visuals { }); } - protected buildDom(width: number): SVGElement { + protected buildDom(): SVGElement { return undefined; } } diff --git a/sim/visuals/wireView.ts b/sim/visuals/wireView.ts index df1dd2a6..901cf573 100644 --- a/sim/visuals/wireView.ts +++ b/sim/visuals/wireView.ts @@ -1,11 +1,10 @@ -/// +/// namespace pxsim.visuals { export class WireView extends View implements LayoutElement { private wire: SVGSVGElement; private path: SVGPathElement; - private selected: boolean; private hasDimensions: boolean; protected startX: number; @@ -30,7 +29,7 @@ namespace pxsim.visuals { this.updatePath(); } - buildDom(width: number): SVGElement { + buildDom(): SVGElement { this.wire = svg.elt("svg", { height: "100%", width: "100%" }) as SVGSVGElement; this.path = pxsim.svg.child(this.wire, "path", { 'd': '', @@ -45,8 +44,8 @@ namespace pxsim.visuals { updatePath() { if (!this.hasDimensions) return; const height = this.endY - this.startY; - const quarterHeight = height / 4; - const middleHeight = this.port == 1 || this.port == 2 ? quarterHeight : quarterHeight * 2; + const thirdHeight = height / 3; + const middleHeight = this.port == 1 || this.port == 2 ? thirdHeight : thirdHeight * 2; let d = `M${this.startX} ${this.startY}`; d += ` L${this.startX} ${this.startY + middleHeight}`; d += ` L${this.endX} ${this.startY + middleHeight}`; @@ -79,7 +78,7 @@ namespace pxsim.visuals { } public setSelected(selected: boolean) { - this.selected = selected; + super.setSelected(selected); this.updateOpacity(); } diff --git a/svgicons/categories/advancedcollapsed.svg b/svgicons/categories/advancedcollapsed.svg deleted file mode 100644 index 087e876a..00000000 --- a/svgicons/categories/advancedcollapsed.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - -color - - - - - - - - - - - - - - - diff --git a/svgicons/categories/advancedexpanded.svg b/svgicons/categories/advancedexpanded.svg deleted file mode 100644 index f4b42b1b..00000000 --- a/svgicons/categories/advancedexpanded.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - -color - - - - - - - - - - - - - - - diff --git a/svgicons/generateIcons.js b/svgicons/generateIcons.js index 3b316ecf..01b276cc 100644 --- a/svgicons/generateIcons.js +++ b/svgicons/generateIcons.js @@ -7,8 +7,6 @@ webfontsGenerator({ "./touch.svg", "./gyro.svg", "./categories/addpackage.svg", - "./categories/advancedcollapsed.svg", - "./categories/advancedexpanded.svg", "./categories/brick.svg", "./categories/controls.svg", "./categories/functions.svg", diff --git a/theme/blockly.less b/theme/blockly.less index 4a15ab27..4bd7b422 100644 --- a/theme/blockly.less +++ b/theme/blockly.less @@ -13,11 +13,14 @@ } .blocklyFlyoutLabel:not(.blocklyFlyoutHeading) .blocklyFlyoutLabelIcon { - font-family: 'legoIcons'; fill: white; font-size: 1.7rem; } +.blocklyFlyoutLabel .blocklyFlyoutLabelIcon { + font-family: 'legoIcons'; +} + span.blocklyTreeIcon { font-family: 'legoIcons' !important; } @@ -73,56 +76,40 @@ span.blocklyTreeLabel { } } -span.blocklyTreeIcon.blocklyTreeIcontests::before { - content: ""; -} - span.blocklyTreeIcon.blocklyTreeIconmusic::before { - content: "\f110"; -} - -span.blocklyTreeIcon.blocklyTreeIconloops::before { - content: "\f10d"; -} - -span.blocklyTreeIcon.blocklyTreeIconlogic::before { - content: "\f10c"; -} - -span.blocklyTreeIcon.blocklyTreeIconvariables::before { - content: "\f113"; -} - -span.blocklyTreeIcon.blocklyTreeIconmath::before { content: "\f10e"; } -span.blocklyTreeIcon.blocklyTreeIconadvancedcollapsed::before { - content: "\f106"; -} - -span.blocklyTreeIcon.blocklyTreeIconadvancedexpanded::before { - content: "\f107"; -} - -span.blocklyTreeIcon.blocklyTreeIconfunctions::before { - content: "\f10a"; -} - -span.blocklyTreeIcon.blocklyTreeIconarrays::before { +span.blocklyTreeIcon.blocklyTreeIconloops::before { content: "\f10b"; } -span.blocklyTreeIcon.blocklyTreeIcontext::before { - content: "\f112"; +span.blocklyTreeIcon.blocklyTreeIconlogic::before { + content: "\f10a"; } -span.blocklyTreeIcon.blocklyTreeIconconsole::before { - content: ""; +span.blocklyTreeIcon.blocklyTreeIconvariables::before { + content: "\f111"; +} + +span.blocklyTreeIcon.blocklyTreeIconmath::before { + content: "\f10c"; +} + +span.blocklyTreeIcon.blocklyTreeIconfunctions::before { + content: "\f108"; +} + +span.blocklyTreeIcon.blocklyTreeIconarrays::before { + content: "\f109"; +} + +span.blocklyTreeIcon.blocklyTreeIcontext::before { + content: "\f110"; } span.blocklyTreeIcon.blocklyTreeIconcontrol::before { - content: "\f109"; + content: "\f107"; } span.blocklyTreeIcon.blocklyTreeIconaddpackage::before { @@ -130,18 +117,18 @@ span.blocklyTreeIcon.blocklyTreeIconaddpackage::before { } .save-editortools-btn .icon.save::before { - content: "\f117" -} - -.download-button .icon.download.icon-and-text::before { - content: "\f116" -} - -.button.approve.positive .checkmark.icon::before { content: "\f115" } -.button.approve.cancel .cancel.icon::before { +.download-button .icon.download.icon-and-text::before { content: "\f114" } +.button.approve.positive .checkmark.icon::before { + content: "\f113" +} + +.button.approve.cancel .cancel.icon::before { + content: "\f112" +} +