Compare commits

...

15 Commits

Author SHA1 Message Date
71fe612ced 0.0.91 2018-02-05 16:38:50 -08:00
e58ec06e91 IR button indexing fixes (#303)
* IR remote id fixes

* hiding private api

* fixing indexing of buttons
2018-02-05 15:11:11 -08:00
538493369b adding button class on remote buttons 2018-02-05 13:57:24 -08:00
56dd8e0875 enabling doc checks 2018-02-03 09:11:49 -08:00
1f7ef637b2 Fix links in target 2018-02-03 09:09:10 -08:00
f4f2e0ba0e 0.0.90 2018-02-02 14:19:17 -08:00
22c31c57df fix port bug 2018-02-02 14:19:07 -08:00
6879961297 0.0.89 2018-02-02 13:39:18 -08:00
69fcb7407a Simulator support for remote (#302)
* fixing up state

* upgrading ir simulator

* displaying remote

* updated infrared svg
2018-02-02 13:38:54 -08:00
4dfada877c Implement simulator sensor for ambient and reflected light (#301)
* Initial work

* More stuff

* Stuff

* Stuff

* Hardcoded dashed line

* High and low thresholds

* Use rect bBox

* Add back grabbing hand

* Threshold placement

* Cleanup

* Don't need defs

* pxtarget

* Remove dashed lines for now
2018-02-02 13:24:50 -08:00
b10b636766 0.0.88 2018-02-02 09:48:51 -08:00
ba47fb0589 Support for remote control buttons (#300)
* refactor beacon function inside IR sensor

* towards sim support

* channel labels

* reverting to singletons

* hiding unused apis

* lazy allocation of button instances

* tracking button state

* hook up the state
2018-02-02 09:48:27 -08:00
f36e14fe69 renaming remote button names 2018-02-01 22:33:05 -08:00
8bab919db2 0.0.87 2018-02-01 22:21:15 -08:00
89a82b54dc Ir proximity in simulator (#299)
* support for IR proximity

* fixing build issue

* missing break

* remove auto-start of sensor

* setting mode on onEvent

* flooring slider value

* bump up proximity

* fixing threshold blocks
2018-02-01 22:03:01 -08:00
19 changed files with 651 additions and 193 deletions

View File

@ -48,7 +48,6 @@
* [show mood](/reference/brick/show-mood) * [show mood](/reference/brick/show-mood)
* [show image](/reference/brick/show-image) * [show image](/reference/brick/show-image)
* [clear screen](/reference/brick/clear-screen) * [clear screen](/reference/brick/clear-screen)
* [print ports](/reference/brick/print-ports)
* [on event](/reference/brick/button/on-event) * [on event](/reference/brick/button/on-event)
* [is pressed](/reference/brick/button/is-pressed) * [is pressed](/reference/brick/button/is-pressed)
* [was pressed](/reference/brick/button/was-pressed) * [was pressed](/reference/brick/button/was-pressed)

View File

@ -19,7 +19,7 @@ brick.buttonEnter.onEvent(ButtonEvent.Bumped, () => {
The editor work in [most modern browsers](/browsers), work [offline](/offline) once loaded and do not require any installation. The editor work in [most modern browsers](/browsers), work [offline](/offline) once loaded and do not require any installation.
## [Compile and Flash: Your Program!](/device/usb) ## Compile and Flash: Your Program!
When you have your code ready, you connect your @boardname@ to a computer via a USB cable When you have your code ready, you connect your @boardname@ to a computer via a USB cable
so it appears as a mounted drive (named **EV3**). so it appears as a mounted drive (named **EV3**).

View File

@ -1,29 +1,37 @@
const enum IrSensorMode { const enum InfraredSensorMode {
None = -1, None = -1,
Proximity = 0, Proximity = 0,
Seek = 1, Seek = 1,
RemoteControl = 2, RemoteControl = 2,
} }
const enum IrRemoteChannel { const enum InfraredRemoteChannel {
//% block="channel 0"
Ch0 = 0, // top Ch0 = 0, // top
//% block="channel 1"
Ch1 = 1, Ch1 = 1,
//% block="channel 2"
Ch2 = 2, Ch2 = 2,
//% block="channel 3"
Ch3 = 3, Ch3 = 3,
} }
const enum IrRemoteButton { const enum InfraredRemoteButton {
None = 0x00, //% block="center beacon"
CenterBeacon = 0x01, CenterBeacon = 0x01,
//% block="top left"
TopLeft = 0x02, TopLeft = 0x02,
//% block="bottom left"
BottomLeft = 0x04, BottomLeft = 0x04,
//% block="top right"
TopRight = 0x08, TopRight = 0x08,
//% block="bottom right"
BottomRight = 0x10, BottomRight = 0x10,
} }
const enum InfraredSensorEvent { const enum InfraredSensorEvent {
//% block="object near" //% block="object near"
ObjectNear = 1, ObjectNear = 3,
//% block="object detected" //% block="object detected"
ObjectDetected = 2 ObjectDetected = 2
} }
@ -31,61 +39,44 @@ const enum InfraredSensorEvent {
namespace sensors { namespace sensors {
function mapButton(v: number) { function mapButton(v: number) {
switch (v) { switch (v) {
case 0: return IrRemoteButton.None case 1: return InfraredRemoteButton.TopLeft;
case 1: return IrRemoteButton.TopLeft case 2: return InfraredRemoteButton.BottomLeft;
case 2: return IrRemoteButton.BottomLeft case 3: return InfraredRemoteButton.TopRight;
case 3: return IrRemoteButton.TopRight case 4: return InfraredRemoteButton.BottomRight;
case 4: return IrRemoteButton.TopRight | IrRemoteButton.BottomRight case 5: return InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight
case 5: return IrRemoteButton.TopLeft | IrRemoteButton.TopRight case 6: return InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight
case 6: return IrRemoteButton.TopLeft | IrRemoteButton.BottomRight case 7: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight
case 7: return IrRemoteButton.BottomLeft | IrRemoteButton.TopRight case 8: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight
case 8: return IrRemoteButton.BottomLeft | IrRemoteButton.BottomRight case 9: return InfraredRemoteButton.CenterBeacon
case 9: return IrRemoteButton.CenterBeacon case 10: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft
case 10: return IrRemoteButton.BottomLeft | IrRemoteButton.TopLeft case 11: return InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight
case 11: return IrRemoteButton.TopRight | IrRemoteButton.BottomRight default: return 0;
default: return IrRemoteButton.None
} }
} }
let buttons: RemoteInfraredBeaconButton[] const __remoteButtons: RemoteInfraredBeaconButton[] = [];
function __irButton(id: InfraredRemoteButton): RemoteInfraredBeaconButton {
function create(ir: InfraredSensor) { for(let i = 0; i < __remoteButtons.length; ++i) {
// it's created by referencing it if (__remoteButtons[i].position == id)
} return __remoteButtons[i];
export function irButton(id: IrRemoteButton): RemoteInfraredBeaconButton {
if (buttons == null) {
buttons = []
for (let i = 0; i < 5; ++i) {
buttons.push(new RemoteInfraredBeaconButton(new brick.Button()))
}
// make sure sensors are up
create(infraredSensor1)
create(infraredSensor2)
create(infraredSensor3)
create(infraredSensor4)
} }
const btn = new RemoteInfraredBeaconButton(id, new brick.Button());
let num = -1 __remoteButtons.push(btn);
while (id) { return btn;
id >>= 1;
num++;
}
num = Math.clamp(0, buttons.length - 1, num)
return buttons[num]
} }
//% fixedInstances //% fixedInstances
export class RemoteInfraredBeaconButton extends control.Component { export class RemoteInfraredBeaconButton extends control.Component {
private button: brick.Button; position: InfraredRemoteButton;
constructor(button: brick.Button) { private _button: brick.Button;
constructor(position: InfraredRemoteButton, button: brick.Button) {
super(); super();
this.button = button; this.position = position;
this._button = button;
} }
_update(curr: boolean) { _update(curr: boolean) {
this.button._update(curr); this._button._update(curr);
} }
/** /**
@ -100,7 +91,7 @@ namespace sensors {
//% weight=81 blockGap=8 //% weight=81 blockGap=8
//% group="Remote Infrared Beacon" //% group="Remote Infrared Beacon"
isPressed() { isPressed() {
return this.button.isPressed(); return this._button.isPressed();
} }
/** /**
@ -115,7 +106,7 @@ namespace sensors {
//% weight=80 //% weight=80
//% group="Remote Infrared Beacon" //% group="Remote Infrared Beacon"
wasPressed() { wasPressed() {
return this.button.wasPressed(); return this._button.wasPressed();
} }
/** /**
@ -131,42 +122,53 @@ namespace sensors {
//% weight=99 blockGap=8 //% weight=99 blockGap=8
//% group="Remote Infrared Beacon" //% group="Remote Infrared Beacon"
onEvent(ev: ButtonEvent, body: () => void) { onEvent(ev: ButtonEvent, body: () => void) {
this.button.onEvent(ev, body); this._button.onEvent(ev, body);
}
/**
* Pauses until the given event is raised
* @param ev the event to wait for
*/
//% help=input/remote-infrared-beacon/pause-until
//% blockId=remoteButtonPauseUntil block="pause until %button|%event"
//% parts="remote"
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Remote Infrared Beacon"
pauseUntil(ev: ButtonEvent) {
this._button.pauseUntil(ev);
} }
} }
//% fixedInstances //% fixedInstances
export class InfraredSensor extends internal.UartSensor { export class InfraredSensor extends internal.UartSensor {
private channel: IrRemoteChannel; private _channel: InfraredRemoteChannel;
private proximityThreshold: sensors.ThresholdDetector; private _proximityThreshold: sensors.ThresholdDetector;
constructor(port: number) { constructor(port: number) {
super(port) super(port)
this.channel = IrRemoteChannel.Ch0 this._channel = InfraredRemoteChannel.Ch0
this.proximityThreshold = new sensors.ThresholdDetector(this._id, 0, 100, 10, 90); this._proximityThreshold = new sensors.ThresholdDetector(this._id, 0, 100, 10, 90);
irButton(0) // make sure buttons array is initalized this.setMode(InfraredSensorMode.Proximity);
// and set the mode, as otherwise button events won't work
this.mode = IrSensorMode.RemoteControl;
} }
_query() { _query() {
if (this.mode == IrSensorMode.RemoteControl) if (this.mode == InfraredSensorMode.RemoteControl)
return mapButton(this.getNumber(NumberFormat.UInt8LE, this.channel)); return mapButton(this.getNumber(NumberFormat.UInt8LE, this._channel));
else if (this.mode == IrSensorMode.Proximity) { else if (this.mode == InfraredSensorMode.Proximity) {
return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff; return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff;
} }
return 0 return 0
} }
_update(prev: number, curr: number) { _update(prev: number, curr: number) {
if (this.mode == IrSensorMode.RemoteControl) { if (this.mode == InfraredSensorMode.RemoteControl) {
for (let i = 0; i < buttons.length; ++i) { for (let i = 0; i < __remoteButtons.length; ++i) {
let v = !!(curr & (1 << i)) const v = !!(curr & __remoteButtons[i].position);
buttons[i]._update(v) __remoteButtons[i]._update(v)
} }
} else if (this.mode == IrSensorMode.Proximity) { } else if (this.mode == InfraredSensorMode.Proximity) {
this.proximityThreshold.setLevel(curr); this._proximityThreshold.setLevel(curr);
} }
} }
@ -174,13 +176,7 @@ namespace sensors {
return DAL.DEVICE_TYPE_IR return DAL.DEVICE_TYPE_IR
} }
setRemoteChannel(c: IrRemoteChannel) { setMode(m: InfraredSensorMode) {
c = Math.clamp(0, 3, c | 0)
this.channel = c
this.setMode(IrSensorMode.RemoteControl)
}
setMode(m: IrSensorMode) {
this._setMode(m) this._setMode(m)
} }
@ -196,6 +192,7 @@ namespace sensors {
//% weight=100 blockGap=8 //% weight=100 blockGap=8
//% group="Infrared Sensor" //% group="Infrared Sensor"
onEvent(event: InfraredSensorEvent, handler: () => void) { onEvent(event: InfraredSensorEvent, handler: () => void) {
this._setMode(InfraredSensorMode.Proximity)
control.onEvent(this._id, event, handler); control.onEvent(this._id, event, handler);
} }
@ -210,6 +207,7 @@ namespace sensors {
//% weight=99 blockGap=8 //% weight=99 blockGap=8
//% group="Infrared Sensor" //% group="Infrared Sensor"
pauseUntil(event: InfraredSensorEvent) { pauseUntil(event: InfraredSensorEvent) {
this._setMode(InfraredSensorMode.Proximity)
control.waitForEvent(this._id, event); control.waitForEvent(this._id, event);
} }
@ -222,36 +220,27 @@ namespace sensors {
//% blockId=infraredGetProximity //% blockId=infraredGetProximity
//% parts="infrared" //% parts="infrared"
//% blockNamespace=sensors //% blockNamespace=sensors
//% weight=65 blockGap=8 //% weight=98 blockGap=8
//% group="Infrared Sensor" //% group="Infrared Sensor"
proximity(): number { proximity(): number {
this._setMode(IrSensorMode.Proximity) this._setMode(InfraredSensorMode.Proximity)
return this.getNumber(NumberFormat.UInt8LE, 0) return this.getNumber(NumberFormat.UInt8LE, 0)
} }
/** /**
* Get the remote commandreceived the infrared sensor. * Sets the remote channel to listen from
* @param sensor the infrared sensor * @param channel the channel to listen
*/ */
//% help=input/infrared/remote-command
//% block="%sensor|remote command"
//% blockId=infraredGetRemoteCommand
//% parts="infrared"
//% blockNamespace=sensors //% blockNamespace=sensors
//% blockId=irSetRemoteChannel block="set %sensor|remote channel to %channel"
//% weight=65 //% weight=65
//% group="Infrared Sensor" //% group="Remote Infrared Beacon"
remoteCommand(): number { setRemoteChannel(channel: InfraredRemoteChannel) {
this._setMode(IrSensorMode.RemoteControl) this.setMode(InfraredSensorMode.RemoteControl)
return this.getNumber(NumberFormat.UInt8LE, this.channel) channel = Math.clamp(0, 3, channel | 0)
this._channel = channel;
} }
// TODO
getDirectionAndDistance() {
this._setMode(IrSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this.channel * 2)
}
/** /**
* Sets a threshold value * Sets a threshold value
* @param condition the dark or bright light condition * @param condition the dark or bright light condition
@ -260,11 +249,11 @@ namespace sensors {
//% blockId=irSetThreshold block="set %sensor|%condition|to %value" //% blockId=irSetThreshold block="set %sensor|%condition|to %value"
//% group="Threshold" blockGap=8 weight=49 //% group="Threshold" blockGap=8 weight=49
//% value.min=0 value.max=100 //% value.min=0 value.max=100
setThreshold(condition: InfraredSensorEvent, value: number) { setPromixityThreshold(condition: InfraredSensorEvent, value: number) {
if (condition == InfraredSensorEvent.ObjectNear) if (condition == InfraredSensorEvent.ObjectNear)
this.proximityThreshold.setLowThreshold(value) this._proximityThreshold.setLowThreshold(value)
else else
this.proximityThreshold.setHighThreshold(value); this._proximityThreshold.setHighThreshold(value);
} }
/** /**
@ -274,8 +263,14 @@ namespace sensors {
//% blockId=irGetThreshold block="%sensor|%condition" //% blockId=irGetThreshold block="%sensor|%condition"
//% group="Threshold" blockGap=8 weight=49 //% group="Threshold" blockGap=8 weight=49
//% sensor.fieldEditor="ports" //% sensor.fieldEditor="ports"
threshold(condition: InfraredSensorEvent): number { proximityThreshold(condition: InfraredSensorEvent): number {
return this.proximityThreshold.threshold(<ThresholdState><number>LightCondition.Dark); return this._proximityThreshold.threshold(<ThresholdState><number>LightCondition.Dark);
}
// TODO
private getDirectionAndDistance() {
this._setMode(InfraredSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this._channel * 2)
} }
} }
@ -291,34 +286,33 @@ namespace sensors {
//% fixedInstance whenUsed block="infrared 4" jres=icons.port4 //% fixedInstance whenUsed block="infrared 4" jres=icons.port4
export const infraredSensor4: InfraredSensor = new InfraredSensor(4) export const infraredSensor4: InfraredSensor = new InfraredSensor(4)
/** /**
* Remote beacon (center) button. * Remote beacon (center) button.
*/ */
//% whenUsed block="center" weight=95 fixedInstance //% whenUsed block="remote button center" weight=95 fixedInstance
export const remoteButtonCenter = irButton(IrRemoteButton.CenterBeacon) export const remoteButtonCenter = __irButton(InfraredRemoteButton.CenterBeacon)
/** /**
* Remote top-left button. * Remote top-left button.
*/ */
//% whenUsed block="top-left" weight=95 fixedInstance //% whenUsed block="remote button top left" weight=95 fixedInstance
export const remoteButtonTopLeft = irButton(IrRemoteButton.TopLeft) export const remoteButtonTopLeft = __irButton(InfraredRemoteButton.TopLeft)
/** /**
* Remote top-right button. * Remote top-right button.
*/ */
//% whenUsed block="top-right" weight=95 fixedInstance //% whenUsed block="remote button top right" weight=95 fixedInstance
export const remoteButtonTopRight = irButton(IrRemoteButton.TopRight) export const remoteButtonTopRight = __irButton(InfraredRemoteButton.TopRight)
/** /**
* Remote bottom-left button. * Remote bottom-left button.
*/ */
//% whenUsed block="bottom-left" weight=95 fixedInstance //% whenUsed block="remote button bottom left" weight=95 fixedInstance
export const remoteButtonBottomLeft = irButton(IrRemoteButton.BottomLeft) export const remoteButtonBottomLeft = __irButton(InfraredRemoteButton.BottomLeft)
/** /**
* Remote bottom-right button. * Remote bottom-right button.
*/ */
//% whenUsed block="bottom-right" weight=95 fixedInstance //% whenUsed block="remote button bottom right" weight=95 fixedInstance
export const remoteButtonBottomRight = irButton(IrRemoteButton.BottomRight) export const remoteButtonBottomRight = __irButton(InfraredRemoteButton.BottomRight)
} }

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "pxt-ev3", "name": "pxt-ev3",
"version": "0.0.86", "version": "0.0.91",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "pxt-ev3", "name": "pxt-ev3",
"version": "0.0.86", "version": "0.0.91",
"description": "LEGO Mindstorms EV3 for Microsoft MakeCode", "description": "LEGO Mindstorms EV3 for Microsoft MakeCode",
"private": true, "private": true,
"keywords": [ "keywords": [

View File

@ -83,7 +83,7 @@
}, },
"appTheme": { "appTheme": {
"accentColor": "#0089BF", "accentColor": "#0089BF",
"logoUrl": "https://lego.makecode.com", "logoUrl": "https://education.lego.com/",
"logo": "./static/lego_education_logo.png", "logo": "./static/lego_education_logo.png",
"highContrastLogo": "./static/lego_education_logo_white.png", "highContrastLogo": "./static/lego_education_logo_white.png",
"docsLogo": "./static/lego-logo.svg", "docsLogo": "./static/lego-logo.svg",
@ -95,12 +95,12 @@
"organizationUrl": "https://makecode.com/", "organizationUrl": "https://makecode.com/",
"organizationLogo": "./static/Microsoft-logo_rgb_c-gray-square.png", "organizationLogo": "./static/Microsoft-logo_rgb_c-gray-square.png",
"organizationWideLogo": "./static/Microsoft-logo_rgb_c-gray.png", "organizationWideLogo": "./static/Microsoft-logo_rgb_c-gray.png",
"homeUrl": "https://lego.makecode.com/", "homeUrl": "https://makecode.legoeducation.com/",
"embedUrl": "https://lego.makecode.com/", "embedUrl": "https://makecode.legoeducation.com/",
"privacyUrl": "https://go.microsoft.com/fwlink/?LinkId=521839", "privacyUrl": "https://go.microsoft.com/fwlink/?LinkId=521839",
"termsOfUseUrl": "https://go.microsoft.com/fwlink/?LinkID=206977", "termsOfUseUrl": "https://go.microsoft.com/fwlink/?LinkID=206977",
"githubUrl": "https://github.com/Microsoft/pxt-ev3", "githubUrl": "https://github.com/Microsoft/pxt-ev3",
"betaUrl": "https://lego.makecode.com/", "betaUrl": "https://makecode.legoeducation.com/about",
"boardName": "LEGO Mindstorms EV3 Brick", "boardName": "LEGO Mindstorms EV3 Brick",
"selectLanguage": true, "selectLanguage": true,
"highContrast": true, "highContrast": true,
@ -157,5 +157,5 @@
"editor.background": "#ecf6ff" "editor.background": "#ecf6ff"
} }
}, },
"ignoreDocsErrors": true "ignoreDocsErrors": false
} }

View File

@ -13,6 +13,7 @@ namespace pxsim {
motorState: EV3MotorState; motorState: EV3MotorState;
screenState: EV3ScreenState; screenState: EV3ScreenState;
audioState: AudioState; audioState: AudioState;
remoteState: RemoteState;
inputNodes: SensorNode[] = []; inputNodes: SensorNode[] = [];
brickNode: BrickNode; brickNode: BrickNode;
@ -38,6 +39,7 @@ namespace pxsim {
this.motorState = new EV3MotorState(); this.motorState = new EV3MotorState();
this.screenState = new EV3ScreenState(); this.screenState = new EV3ScreenState();
this.audioState = new AudioState(); this.audioState = new AudioState();
this.remoteState = new RemoteState();
} }
receiveMessage(msg: SimulatorMessage) { receiveMessage(msg: SimulatorMessage) {
@ -143,6 +145,7 @@ namespace pxsim {
case DAL.DEVICE_TYPE_COLOR: this.inputNodes[port] = new ColorSensorNode(port); break; case DAL.DEVICE_TYPE_COLOR: this.inputNodes[port] = new ColorSensorNode(port); break;
case DAL.DEVICE_TYPE_TOUCH: this.inputNodes[port] = new TouchSensorNode(port); break; case DAL.DEVICE_TYPE_TOUCH: this.inputNodes[port] = new TouchSensorNode(port); break;
case DAL.DEVICE_TYPE_ULTRASONIC: this.inputNodes[port] = new UltrasonicSensorNode(port); break; case DAL.DEVICE_TYPE_ULTRASONIC: this.inputNodes[port] = new UltrasonicSensorNode(port); break;
case DAL.DEVICE_TYPE_IR: this.inputNodes[port] = new InfraredSensorNode(port); break;
} }
} }
return this.inputNodes[port]; return this.inputNodes[port];

81
sim/state/infrared.ts Normal file
View File

@ -0,0 +1,81 @@
/// <reference path="./sensor.ts"/>
namespace pxsim {
export enum InfraredRemoteButton {
//% block="center beacon"
CenterBeacon = 0x01,
//% block="top left"
TopLeft = 0x02,
//% block="bottom left"
BottomLeft = 0x04,
//% block="top right"
TopRight = 0x08,
//% block="bottom right"
BottomRight = 0x10,
}
export class RemoteState {
state: number = 0;
constructor() {
}
unmapButtons() {
switch(this.state) {
case InfraredRemoteButton.TopLeft: return 1;
case InfraredRemoteButton.BottomLeft: return 2;
case InfraredRemoteButton.TopRight: return 3;
case InfraredRemoteButton.BottomRight: return 4;
case InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight: return 5;
case InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight: return 6;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight: return 7;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight: return 8;
case InfraredRemoteButton.CenterBeacon: return 9;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft: return 10;
case InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight: return 11;
default: return 0;
}
}
setPressed(btns: InfraredRemoteButton, down: boolean) {
if (down) this.state = this.state | btns;
else this.state = ~(~this.state | btns);
}
}
export enum InfraredSensorMode {
None = -1,
Proximity = 0,
Seek = 1,
RemoteControl = 2
}
export class InfraredSensorNode extends UartSensorNode {
id = NodeType.InfraredSensor;
private proximity: number = 50; // [0..100]
constructor(port: number) {
super(port);
}
getDeviceType() {
return DAL.DEVICE_TYPE_IR;
}
setPromixity(proximity: number) {
if (this.proximity != proximity) {
this.proximity = proximity;
this.setChangedState();
}
}
getValue() {
switch(this.mode) {
case InfraredSensorMode.Proximity: return this.proximity;
case InfraredSensorMode.RemoteControl: return ev3board().remoteState.unmapButtons();
default: return 0;
}
}
}
}

View File

@ -7,7 +7,8 @@ namespace pxsim {
LargeMotor = 4, LargeMotor = 4,
GyroSensor = 5, GyroSensor = 5,
ColorSensor = 6, ColorSensor = 6,
UltrasonicSensor = 7 UltrasonicSensor = 7,
InfraredSensor = 8
} }
export interface Node { export interface Node {

View File

@ -0,0 +1,21 @@
namespace pxsim.visuals {
export const REMOVE_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" width="371.7" height="418.7" viewBox="0 0 98.3 110.8" id="svg8">
<g id="layer1" transform="translate(0 -186.2)">
<rect id="rect90-3" width="72.7" height="9.6" x="12.1" y="186.5" rx="2.1" fill="#666" stroke="#000" stroke-width=".5"/>
<rect id="rect90" width="73.1" height="9.6" x="12.2" y="287.1" rx="2.1" stroke="#000" stroke-width=".5"/>
<path d="M14.3 193.2c-1.1 0-2.1 1-2.1 2.1v15.5H2.4c-1.2 0-2.1 1-2.1 2.1v73.7c0 1.1 1 2.1 2.1 2.1h9.8v.6c0 1.2 1 2.1 2.1 2.1h68.3c1.2 0 2.2-1 2.2-2.1v-.6h11.1c1.2 0 2.2-1 2.2-2.1v-73.7c0-1.1-1-2.1-2.2-2.1H84.8v-15.5c0-1.2-1-2.1-2.2-2.1z" id="rect10" fill="#fff" stroke="#000" stroke-width="1.1"/>
<path d="M31 197.9c7.3 0 7.7 8.2 7.7 9.3v17.4c0 1.2-1 2.2-2.2 2.2H25.7c-1.2 0-2.2-1-2.2-2.2v-17c0-1.1.2-9.7 7.5-9.7z" id="topleft" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M65.5 197.9c7.3 0 7.7 8.2 7.7 9.3v17.4c0 1.2-1 2.2-2.2 2.2H60.2c-1.2 0-2.1-1-2.1-2.2v-17c0-1.1.2-9.7 7.4-9.7z" id="topright" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M65.7 266.3c-7.3 0-7.6-8.2-7.6-9.4v-17.4c0-1.2 1-2.2 2.1-2.2H71c1.2 0 2.2 1 2.2 2.2v17c0 1.2-.2 9.8-7.5 9.8z" id="bottomright" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M31.2 266.3c-7.3 0-7.7-8.2-7.7-9.4v-17.4c0-1.2 1-2.2 2.2-2.2h10.8c1.2 0 2.2 1 2.2 2.2v17c0 1.2-.3 9.8-7.5 9.8z" id="bottomleft" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M27.5 273.6l40.1.1c1.2 0 5.4 6.3 5.4 7.5v5.8c0 1.1-1 2.1-2.2 2.1H25.5c-1.2 0-2.1-1-2.1-2.1v-5.5c0-1.2 3-7.9 4.1-7.9z" id="center" fill="#b3b3b3" stroke="#000" stroke-width="1.1"/>
<rect id="rect107" width="15.1" height="3.2" x="23.1" y="231.1" ry="1.3" fill="#247aff" stroke-width=".5"/>
<rect id="rect107-1" width="15.1" height="3.2" x="58.2" y="231.1" ry="1.3" fill="#ff242b" stroke-width=".5"/>
<circle id="path941" cx="49.2" cy="263.3" r="2.5" fill="#ff242b" stroke-width=".5"/>
<circle id="path943" cx="49.2" cy="263.3" r="4.5" fill="none" stroke="#949494" stroke-width=".5"/>
</g>
</svg>
`;
}

View File

@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" id="svg5190" viewBox="0 0 83.5 35.1">
<defs id="defs961">
<clipPath id="clip-path" transform="translate(0 -22.4)">
<circle cx="17.5" cy="40" r="7" id="circle955" fill="none"/>
</clipPath>
<clipPath id="clip-path-2" transform="translate(0 -22.4)">
<circle cx="65.9" cy="40" r="7" id="circle958" fill="none"/>
</clipPath>
</defs>
<path data-name="US main black2" id="US_main_black2" fill="#242424" d="M22.6 1.5H66v31.3H22.6z"/>
<path data-name="US main black1" id="US_main_black1" d="M30.2 8.5h25.3v17.6H30.2z"/>
<g id="US_eye1_net" data-name="US eye1 net">
<g clip-path="url(#clip-path)" id="g991">
<g id="US_eye1_net_mask" data-name="US eye1 net mask">
<g id="US_eye1_net_total" data-name="US eye1 net total" fill="#aa7707">
<path id="US_eye1_net14" data-name="US eye1 net14" transform="rotate(-30 -30.7 33.6)" d="M10.8 33.5h.6v22.8h-.6z"/>
<path id="US_eye1_net13" data-name="US eye1 net13" transform="rotate(-30 -18.8 36.5)" d="M11.8 47.4h22.5v.6H11.8z"/>
<path id="US_eye1_net12" data-name="US eye1 net12" transform="rotate(-30 -28.2 32.2)" d="M13.3 32.1h.6v22.8h-.6z"/>
<path id="US_eye1_net11" data-name="US eye1 net11" transform="rotate(-30 -20.2 34)" d="M10.4 45h22.5v.6H10.4z"/>
<path id="US_eye1_net10" data-name="US eye1 net10" transform="rotate(-30 -25.7 30.8)" d="M15.8 30.6h.6v22.8h-.6z"/>
<path id="US_eye1_net9" data-name="US eye1 net9" transform="rotate(-30 -21.7 31.5)" d="M8.9 42.5h22.5v.6H8.9z"/>
<path id="US_eye1_net8" data-name="US eye1 net8" transform="rotate(-30 -23.2 29.3)" d="M18.3 29.2h.6V52h-.6z"/>
<path id="US_eye1_net7" data-name="US eye1 net7" transform="rotate(-30 -23.1 29)" d="M7.5 40H30v.6H7.5z"/>
<path id="US_eye1_net6" data-name="US eye1 net6" transform="rotate(-30 -20.7 27.9)" d="M20.8 27.8h.6v22.8h-.6z"/>
<path id="US_eye1_net5" data-name="US eye1 net5" transform="rotate(-30 -24.6 26.5)" d="M6 37.5h22.5v.6H6z"/>
<path id="US_eye1_net4" data-name="US eye1 net4" transform="rotate(-30 -18.2 26.4)" d="M23.3 26.3h.6v22.8h-.6z"/>
<path id="US_eye1_net3" data-name="US eye1 net3" transform="rotate(-30 -26 24)" d="M4.6 35h22.5v.6H4.6z"/>
<path id="US_eye1_net2" data-name="US eye1 net2" transform="rotate(-30 -15.7 25)" d="M25.8 24.9h.6v22.8h-.6z"/>
<path id="US_eye1_net1" data-name="US eye1 net1" transform="rotate(-30 -27.4 21.5)" d="M3.2 32.5h22.5v.6H3.2z"/>
</g>
</g>
</g>
</g>
<g id="g1656">
<g data-name="US eye1 black" id="US_eye1_black">
<circle id="circle967" r="17.4" cy="17.6" cx="17.6" stroke="#b3b3b3" stroke-width=".3" stroke-miterlimit="10"/>
<circle id="circle969" r="17.3" cy="17.6" cx="17.6" fill="none"/>
</g>
<path id="path1650" d="M5.8 17h19.7l3.1 3.6" fill="none" stroke="#e80000" stroke-width="1.7"/>
</g>
<g transform="matrix(-1 0 0 1 83.5 0)" id="g1656-6">
<g data-name="US eye1 black" id="US_eye1_black-9">
<circle id="circle967-5" r="17.4" cy="17.6" cx="17.6" stroke="#b3b3b3" stroke-width=".3" stroke-miterlimit="10"/>
<circle id="circle969-1" r="17.3" cy="17.6" cx="17.6" fill="none"/>
</g>
<path id="path1650-4" d="M5.8 17h19.7l3.1 3.6" fill="none" stroke="#e80000" stroke-width="1.7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,53 @@
namespace pxsim {
export const INFRARED_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" id="svg5190" viewBox="0 0 83.5 35.1">
<defs id="defs961">
<clipPath id="clip-path" transform="translate(0 -22.4)">
<circle cx="17.5" cy="40" r="7" id="circle955" fill="none"/>
</clipPath>
<clipPath id="clip-path-2" transform="translate(0 -22.4)">
<circle cx="65.9" cy="40" r="7" id="circle958" fill="none"/>
</clipPath>
</defs>
<path data-name="US main black2" id="US_main_black2" fill="#242424" d="M22.6 1.5H66v31.3H22.6z"/>
<path data-name="US main black1" id="US_main_black1" d="M30.2 8.5h25.3v17.6H30.2z"/>
<g id="US_eye1_net" data-name="US eye1 net">
<g clip-path="url(#clip-path)" id="g991">
<g id="US_eye1_net_mask" data-name="US eye1 net mask">
<g id="US_eye1_net_total" data-name="US eye1 net total" fill="#aa7707">
<path id="US_eye1_net14" data-name="US eye1 net14" transform="rotate(-30 -30.7 33.6)" d="M10.8 33.5h.6v22.8h-.6z"/>
<path id="US_eye1_net13" data-name="US eye1 net13" transform="rotate(-30 -18.8 36.5)" d="M11.8 47.4h22.5v.6H11.8z"/>
<path id="US_eye1_net12" data-name="US eye1 net12" transform="rotate(-30 -28.2 32.2)" d="M13.3 32.1h.6v22.8h-.6z"/>
<path id="US_eye1_net11" data-name="US eye1 net11" transform="rotate(-30 -20.2 34)" d="M10.4 45h22.5v.6H10.4z"/>
<path id="US_eye1_net10" data-name="US eye1 net10" transform="rotate(-30 -25.7 30.8)" d="M15.8 30.6h.6v22.8h-.6z"/>
<path id="US_eye1_net9" data-name="US eye1 net9" transform="rotate(-30 -21.7 31.5)" d="M8.9 42.5h22.5v.6H8.9z"/>
<path id="US_eye1_net8" data-name="US eye1 net8" transform="rotate(-30 -23.2 29.3)" d="M18.3 29.2h.6V52h-.6z"/>
<path id="US_eye1_net7" data-name="US eye1 net7" transform="rotate(-30 -23.1 29)" d="M7.5 40H30v.6H7.5z"/>
<path id="US_eye1_net6" data-name="US eye1 net6" transform="rotate(-30 -20.7 27.9)" d="M20.8 27.8h.6v22.8h-.6z"/>
<path id="US_eye1_net5" data-name="US eye1 net5" transform="rotate(-30 -24.6 26.5)" d="M6 37.5h22.5v.6H6z"/>
<path id="US_eye1_net4" data-name="US eye1 net4" transform="rotate(-30 -18.2 26.4)" d="M23.3 26.3h.6v22.8h-.6z"/>
<path id="US_eye1_net3" data-name="US eye1 net3" transform="rotate(-30 -26 24)" d="M4.6 35h22.5v.6H4.6z"/>
<path id="US_eye1_net2" data-name="US eye1 net2" transform="rotate(-30 -15.7 25)" d="M25.8 24.9h.6v22.8h-.6z"/>
<path id="US_eye1_net1" data-name="US eye1 net1" transform="rotate(-30 -27.4 21.5)" d="M3.2 32.5h22.5v.6H3.2z"/>
</g>
</g>
</g>
</g>
<g id="g1656">
<g data-name="US eye1 black" id="US_eye1_black">
<circle id="circle967" r="17.4" cy="17.6" cx="17.6" stroke="#b3b3b3" stroke-width=".3" stroke-miterlimit="10"/>
<circle id="circle969" r="17.3" cy="17.6" cx="17.6" fill="none"/>
</g>
<path id="path1650" d="M5.8 17h19.7l3.1 3.6" fill="none" stroke="#e80000" stroke-width="1.7"/>
</g>
<g transform="matrix(-1 0 0 1 83.5 0)" id="g1656-6">
<g data-name="US eye1 black" id="US_eye1_black-9">
<circle id="circle967-5" r="17.4" cy="17.6" cx="17.6" stroke="#b3b3b3" stroke-width=".3" stroke-miterlimit="10"/>
<circle id="circle969-1" r="17.3" cy="17.6" cx="17.6" fill="none"/>
</g>
<path id="path1650-4" d="M5.8 17h19.7l3.1 3.6" fill="none" stroke="#e80000" stroke-width="1.7"/>
</g>
</svg>
`;
}

View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="371.7" height="418.7" viewBox="0 0 98.3 110.8" id="svg8">
<g id="layer1" transform="translate(0 -186.2)">
<rect id="rect90-3" width="72.7" height="9.6" x="12.1" y="186.5" rx="2.1" fill="#666" stroke="#000" stroke-width=".5"/>
<rect id="rect90" width="73.1" height="9.6" x="12.2" y="287.1" rx="2.1" stroke="#000" stroke-width=".5"/>
<path d="M14.3 193.2c-1.1 0-2.1 1-2.1 2.1v15.5H2.4c-1.2 0-2.1 1-2.1 2.1v73.7c0 1.1 1 2.1 2.1 2.1h9.8v.6c0 1.2 1 2.1 2.1 2.1h68.3c1.2 0 2.2-1 2.2-2.1v-.6h11.1c1.2 0 2.2-1 2.2-2.1v-73.7c0-1.1-1-2.1-2.2-2.1H84.8v-15.5c0-1.2-1-2.1-2.2-2.1z" id="rect10" fill="#fff" stroke="#000" stroke-width="1.1"/>
<path d="M31 197.9c7.3 0 7.7 8.2 7.7 9.3v17.4c0 1.2-1 2.2-2.2 2.2H25.7c-1.2 0-2.2-1-2.2-2.2v-17c0-1.1.2-9.7 7.5-9.7z" id="topleft" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M65.5 197.9c7.3 0 7.7 8.2 7.7 9.3v17.4c0 1.2-1 2.2-2.2 2.2H60.2c-1.2 0-2.1-1-2.1-2.2v-17c0-1.1.2-9.7 7.4-9.7z" id="topright" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M65.7 266.3c-7.3 0-7.6-8.2-7.6-9.4v-17.4c0-1.2 1-2.2 2.1-2.2H71c1.2 0 2.2 1 2.2 2.2v17c0 1.2-.2 9.8-7.5 9.8z" id="bottomright" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M31.2 266.3c-7.3 0-7.7-8.2-7.7-9.4v-17.4c0-1.2 1-2.2 2.2-2.2h10.8c1.2 0 2.2 1 2.2 2.2v17c0 1.2-.3 9.8-7.5 9.8z" id="bottomleft" fill="#ccc" stroke="#000" stroke-width="1.1"/>
<path d="M27.5 273.6l40.1.1c1.2 0 5.4 6.3 5.4 7.5v5.8c0 1.1-1 2.1-2.2 2.1H25.5c-1.2 0-2.1-1-2.1-2.1v-5.5c0-1.2 3-7.9 4.1-7.9z" id="center" fill="#b3b3b3" stroke="#000" stroke-width="1.1"/>
<rect id="rect107" width="15.1" height="3.2" x="23.1" y="231.1" ry="1.3" fill="#247aff" stroke-width=".5"/>
<rect id="rect107-1" width="15.1" height="3.2" x="58.2" y="231.1" ry="1.3" fill="#ff242b" stroke-width=".5"/>
<circle id="path941" cx="49.2" cy="263.3" r="2.5" fill="#ff242b" stroke-width=".5"/>
<circle id="path943" cx="49.2" cy="263.3" r="4.5" fill="none" stroke="#949494" stroke-width=".5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -230,6 +230,14 @@ namespace pxsim.visuals {
view = new DistanceSliderControl(this.element, this.defs, state, port); view = new DistanceSliderControl(this.element, this.defs, state, port);
break; break;
} }
case NodeType.InfraredSensor: {
const state = ev3board().getInputNodes()[port] as InfraredSensorNode;
if (state.getMode() == InfraredSensorMode.Proximity)
view = new ProximitySliderControl(this.element, this.defs, state, port);
else if (state.getMode() == InfraredSensorMode.RemoteControl)
view = new RemoteBeaconButtonsControl(this.element, this.defs, state, port);
break;
}
case NodeType.GyroSensor: { case NodeType.GyroSensor: {
const state = ev3board().getInputNodes()[port] as GyroSensorNode; const state = ev3board().getInputNodes()[port] as GyroSensorNode;
view = new RotationSliderControl(this.element, this.defs, state, port); view = new RotationSliderControl(this.element, this.defs, state, port);
@ -271,6 +279,8 @@ namespace pxsim.visuals {
view = new ColorSensorView(port); break; view = new ColorSensorView(port); break;
case NodeType.UltrasonicSensor: case NodeType.UltrasonicSensor:
view = new UltrasonicSensorView(port); break; view = new UltrasonicSensorView(port); break;
case NodeType.InfraredSensor:
view = new InfraredView(port); break;
case NodeType.Brick: case NodeType.Brick:
//return new BrickView(0); //return new BrickView(0);
view = this.layoutView.getBrick(); break; view = this.layoutView.getBrick(); break;
@ -309,7 +319,7 @@ namespace pxsim.visuals {
this.layoutView.unselectBrick(); this.layoutView.unselectBrick();
this.resize(); this.resize();
}); });
const brick =new BrickView(-1); const brick = new BrickView(-1);
brick.setSelected(EV3View.isPreviousBrickSelected()); brick.setSelected(EV3View.isPreviousBrickSelected());
this.layoutView.setBrick(brick, brickCloseIcon); this.layoutView.setBrick(brick, brickCloseIcon);

View File

@ -1,64 +1,33 @@
namespace pxsim.visuals { namespace pxsim.visuals {
export class ColorWheelControl extends ControlView<ColorSensorNode> { export class ColorWheelControl extends ControlView<ColorSensorNode> {
private group: SVGGElement; private group: SVGGElement;
private colorGradient: SVGLinearGradientElement; private colorGradient: SVGLinearGradientElement;
private defs: SVGDefsElement; private reporter: SVGTextElement;
private rect: SVGElement;
getInnerView(parent: SVGSVGElement) {
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
this.group = svg.elt("g") as SVGGElement;
this.group.setAttribute("transform", `translate(12, 0) scale(2)`)
let gc = "gradient-color";
this.colorGradient = svg.linearGradient(this.defs, gc, true);
svg.setGradientValue(this.colorGradient, "50%");
svg.setGradientColors(this.colorGradient, "black", "white");
const circle = pxsim.svg.child(this.group, "g");
const innerCircle = pxsim.svg.child(circle, "circle",
{cursor: '-webkit-grab',
fill: `url(#${gc})`,
r: 17,
cx: 13,
cy: 20,
stroke: 'black',
'stroke-width': 2
});
let pt = parent.createSVGPoint();
let captured = false;
touchEvents(circle,
ev => {
if (captured && (ev as MouseEvent).clientX) {
ev.preventDefault();
this.setColor(pt, parent, ev as MouseEvent);
}
},
ev => {
captured = true;
if ((ev as MouseEvent).clientX) {
this.setColor(pt, parent, ev as MouseEvent);
}
},
ev => {
captured = false;
},
ev => {
captured = false;
}
)
return this.group;
}
getInnerWidth() { getInnerWidth() {
return CONTROL_WIDTH; return 111;
} }
getInnerHeight() { getInnerHeight() {
return CONTROL_WIDTH; return 192;
}
private getReporterHeight() {
return 58;
}
private getSliderWidth() {
return 62;
}
private getSliderHeight() {
return 111;
}
private getMax() {
return 100;
} }
updateState() { updateState() {
@ -67,16 +36,68 @@ namespace pxsim.visuals {
} }
const node = this.state; const node = this.state;
const percentage = node.getValue(); const percentage = node.getValue();
svg.setGradientValue(this.colorGradient, percentage + "%"); const inversePercentage = this.getMax() - percentage;
svg.setGradientValue(this.colorGradient, inversePercentage + "%");
this.reporter.textContent = `${parseFloat((percentage).toString()).toFixed(0)}`;
} }
private setColor(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { updateColorLevel(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
const width = CONTROL_WIDTH;
let cur = svg.cursorPoint(pt, parent, ev); let cur = svg.cursorPoint(pt, parent, ev);
let t = Math.max(0, Math.min(1, (width + this.left / this.scaleFactor - cur.x / this.scaleFactor) / width)); const bBox = this.rect.getBoundingClientRect();
const height = bBox.height;
let t = Math.max(0, Math.min(1, (height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height));
const state = this.state; const state = this.state;
state.setColor((1-t)*100); state.setColor(t * this.getMax());
}
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
this.group = svg.elt("g") as SVGGElement;
let gc = "gradient-color";
this.colorGradient = svg.linearGradient(globalDefs, gc, false);
svg.setGradientValue(this.colorGradient, "50%");
svg.setGradientColors(this.colorGradient, "black", "yellow");
const reporterGroup = pxsim.svg.child(this.group, "g");
reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 50)`);
this.reporter = pxsim.svg.child(reporterGroup, "text", { 'text-anchor': 'middle', 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement;
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": 0,
"y": 0,
"width": this.getSliderWidth(),
"height": this.getSliderHeight(),
"style": `fill: url(#${gc})`
}
)
this.rect = rect;
let pt = parent.createSVGPoint();
let captured = false;
touchEvents(rect, ev => {
if (captured && (ev as MouseEvent).clientY) {
ev.preventDefault();
this.updateColorLevel(pt, parent, ev as MouseEvent);
}
}, ev => {
captured = true;
if ((ev as MouseEvent).clientY) {
rect.setAttribute('cursor', '-webkit-grabbing');
this.updateColorLevel(pt, parent, ev as MouseEvent);
}
}, () => {
captured = false;
rect.setAttribute('cursor', '-webkit-grab');
}, () => {
captured = false;
rect.setAttribute('cursor', '-webkit-grab');
})
return this.group;
} }
} }
} }

View File

@ -0,0 +1,138 @@
namespace pxsim.visuals {
export class ProximitySliderControl extends ControlView<InfraredSensorNode> {
private group: SVGGElement;
private gradient: SVGLinearGradientElement;
private slider: SVGGElement;
private reporter: SVGTextElement;
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', '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 reporterGroup = pxsim.svg.child(this.group, "g");
reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 42)`);
this.reporter = pxsim.svg.child(reporterGroup, "text", { 'text-anchor': 'middle', 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement;
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': ProximitySliderControl.SLIDER_SIDE_PADDING, 'y': 2, 'width': this.getSliderWidth() - ProximitySliderControl.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.getSliderWidth(), 'height': ProximitySliderControl.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': ProximitySliderControl.SLIDER_HANDLE_HEIGHT - 1, 'rx': '1.5', 'ry': '1.5', 'style': 'fill: none;stroke: #b32e29' });
const dragSurface = svg.child(this.group, "rect", {
x: 0,
y: 0,
width: this.getInnerWidth(),
height: this.getInnerHeight(),
opacity: 0,
cursor: '-webkit-grab'
})
let pt = parent.createSVGPoint();
let captured = false;
touchEvents(dragSurface, ev => {
if (captured && (ev as MouseEvent).clientY != undefined) {
ev.preventDefault();
this.updateSliderValue(pt, parent, ev as MouseEvent);
}
}, ev => {
captured = true;
if ((ev as MouseEvent).clientY != undefined) {
dragSurface.setAttribute('cursor', '-webkit-grabbing');
this.updateSliderValue(pt, parent, ev as MouseEvent);
}
}, () => {
captured = false;
dragSurface.setAttribute('cursor', '-webkit-grab');
}, () => {
captured = false;
dragSurface.setAttribute('cursor', '-webkit-grab');
})
return this.group;
}
getInnerHeight() {
return 192;
}
getInnerWidth() {
return 111;
}
private getReporterHeight() {
return 50;
}
private getSliderHeight() {
return 110;
}
private getSliderWidth() {
return 62;
}
updateState() {
if (!this.visible) {
return;
}
const node = this.state;
const percentage = node.getValue();
const y = this.getSliderHeight() * percentage / this.getMax();
this.slider.setAttribute("transform", `translate(0, ${y - ProximitySliderControl.SLIDER_HANDLE_HEIGHT / 2})`);
// 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.getSliderHeight();
const bBox = this.content.getBoundingClientRect();
let t = Math.max(0, Math.min(1, (ProximitySliderControl.SLIDER_HANDLE_HEIGHT + height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height))
const state = this.state;
const v = Math.floor((1 - t) * (this.getMax()));
state.setPromixity(v);
}
private getMin() {
return 0;
}
private getMax() {
return 100;
}
private getGradientDefinition(): LinearGradientDefinition {
return {
stops: [
{ offset: 0, color: '#626262' },
{ offset: 100, color: "#ddd" }
]
};
}
}
}

View File

@ -0,0 +1,57 @@
namespace pxsim.visuals {
enum InfraredRemoteButton {
CenterBeacon = 0x01,
TopLeft = 0x02,
BottomLeft = 0x04,
TopRight = 0x08,
BottomRight = 0x10,
}
export class RemoteBeaconButtonsControl extends ControlView<InfraredSensorNode> {
private group: SVGGElement;
private id = Math.random().toString();
getInnerView() {
this.group = svg.elt("g") as SVGGElement;
this.group.setAttribute("transform", `scale(0.25, 0.25)`)
const xml = pxsim.visuals.normalizeXml(this.id, pxsim.visuals.REMOVE_SVG);
const content = svg.parseString(xml);
this.group.appendChild(content);
const btns: Map<InfraredRemoteButton> = {
"center": InfraredRemoteButton.CenterBeacon,
"topleft": InfraredRemoteButton.TopLeft,
"topright": InfraredRemoteButton.TopRight,
"bottomleft": InfraredRemoteButton.BottomLeft,
"bottomright": InfraredRemoteButton.BottomRight
}
Object.keys(btns).forEach(bid => {
const cid = btns[bid];
const bel = content.getElementById(pxsim.visuals.normalizeId(this.id, bid));
bel.setAttribute("class", "sim-button");
pointerEvents.down.forEach(evid => bel.addEventListener(evid, ev => {
ev3board().remoteState.setPressed(cid, true);
}));
bel.addEventListener(pointerEvents.leave, ev => {
ev3board().remoteState.setPressed(cid, false);
});
bel.addEventListener(pointerEvents.up, ev => {
ev3board().remoteState.setPressed(cid, false);
});
});
return this.group;
}
getInnerWidth() {
return 98.3;
}
getInnerHeight() {
return 110.8;
}
}
}

View File

@ -0,0 +1,10 @@
/// <reference path="./moduleView.ts" />
namespace pxsim.visuals {
export class InfraredView extends ModuleView implements LayoutElement {
constructor(port: number) {
super(INFRARED_SVG, "infrared", NodeType.InfraredSensor, port);
}
}
}

View File

@ -1,4 +1,20 @@
namespace pxsim.visuals { namespace pxsim.visuals {
export function normalizeId(prefix: string, svgId: string) {
return `${prefix}-${svgId}`;
}
export function normalizeXml(prefix: string, xml: string): string {
xml = xml.replace(/id=\"(.*?)\"/g, (m: string, id: string) => {
return `id="${normalizeId(prefix, id)}"`;
});
xml = xml.replace(/url\(#(.*?)\)/g, (m: string, id: string) => {
return `url(#${normalizeId(prefix, id)})`;
});
xml = xml.replace(/xlink:href=\"#(.*?)\"/g, (m: string, id: string) => {
return `xlink:href="#${normalizeId(prefix, id)}"`;
});
return xml;
}
export class ModuleView extends View implements LayoutElement { export class ModuleView extends View implements LayoutElement {
protected content: SVGSVGElement; protected content: SVGSVGElement;
@ -9,21 +25,11 @@ namespace pxsim.visuals {
constructor(protected xml: string, protected prefix: string, protected id: NodeType, protected port: NodeType) { constructor(protected xml: string, protected prefix: string, protected id: NodeType, protected port: NodeType) {
super(); super();
this.xml = this.normalizeXml(xml); this.xml = normalizeXml(this.prefix, xml);
} }
private normalizeXml(xml: string) { private normalizeXml(xml: string) {
const prefix = this.prefix; return pxsim.visuals.normalizeXml(this.prefix, xml);
xml = xml.replace(/id=\"(.*?)\"/g, (m: string, id: string) => {
return `id="${this.normalizeId(id)}"`;
});
xml = xml.replace(/url\(#(.*?)\)/g, (m: string, id: string) => {
return `url(#${this.normalizeId(id)})`;
});
xml = xml.replace(/xlink:href=\"#(.*?)\"/g, (m: string, id: string) => {
return `xlink:href="#${this.normalizeId(id)}"`;
});
return xml;
} }
protected normalizeId(svgId: string) { protected normalizeId(svgId: string) {