Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
fc3a02cc41 | |||
80f2ff6757 | |||
6f790d167c | |||
ac7502074a | |||
50473255a8 | |||
910772d54e | |||
33f12f9ecc | |||
01fa4ef53a | |||
3858b0a0a0 | |||
da30afb121 | |||
086bcf372f | |||
236e7337e2 | |||
4cf223271f | |||
10180f4729 | |||
618dd33221 | |||
7356e5e52e | |||
a59d148eb5 | |||
34ce687bbd | |||
5e241ed566 | |||
dcb2b21e66 | |||
95669e174a | |||
aca7d6113d | |||
ef4b06a087 | |||
afa69c23c4 | |||
8229e71d0a | |||
93770e5821 | |||
cb8e28beb0 | |||
8c7e6055ff | |||
8450db55ac | |||
c63e2c85f1 | |||
d8fc11a688 | |||
847a29848d | |||
39f4372ee5 | |||
a8bbbea8ed | |||
91a8d05e45 | |||
3ecedd3a32 | |||
b6390d77dd | |||
e8f9e0f023 | |||
2bacb58fdd | |||
af8babfea2 | |||
1ee6274100 | |||
7448db5b98 | |||
983645403b | |||
7a9f382bee | |||
e59fd8469b | |||
008c886de9 | |||
c87fa30738 | |||
dd9d1299fa | |||
6a7d1bd95c | |||
dfe270d259 | |||
6deb0683b6 | |||
0a746f5bbe | |||
066408c317 | |||
90f7b229d1 | |||
c54e0b0e9d | |||
ea088ac828 | |||
29cf4d3e53 | |||
6d637eefd7 | |||
6310e96ac2 | |||
dbb1e31822 | |||
5376bc2dce | |||
57bd293df8 | |||
e570d46755 | |||
d37f284fce | |||
b716226889 | |||
1bd19cc1a0 | |||
ac8c9c5e85 | |||
057260c483 | |||
568ae32f81 | |||
ae605ff829 | |||
f25ddb1085 | |||
f175f973f0 | |||
c7e5fa490e | |||
61a29f7c67 | |||
50293fc654 | |||
a9ecadaf09 | |||
6e56107eb5 | |||
1f077beb65 | |||
8fff762156 | |||
cebded2526 | |||
13f53d730a | |||
8adcab1cab | |||
8cb5f442f9 | |||
96448d5237 | |||
30f01bb0ac | |||
3d0b397de2 | |||
e0de55d689 | |||
7637a98f07 | |||
95f94e0886 | |||
9a2367cf8e | |||
15fecb77c4 | |||
4f69bbabfb | |||
8351ed0513 | |||
c34b0a1aeb | |||
9bcd44d7e4 | |||
3403da8ce8 | |||
39c146329f | |||
8d0d0a7e9a | |||
61b3783dd4 | |||
da16428842 | |||
359c456577 | |||
b3d9c167e1 | |||
78398b220f | |||
a656fbbd7b | |||
e681cc8c97 | |||
03beec9c49 | |||
1600644be5 | |||
a3dd64eb4c | |||
b865cce44e | |||
dc42900c7f | |||
dcc3bd95fe | |||
4070d4e691 | |||
7048156b46 | |||
232758805b | |||
211d4e5538 | |||
374d8c590d |
@ -53,11 +53,6 @@ pxt update
|
||||
|
||||
More instructions at https://github.com/Microsoft/pxt#running-a-target-from-localhost
|
||||
|
||||
## Universal Windows App
|
||||
|
||||
The Windows 10 app is a [Universal Windows Hosted Web App](https://microsoftedge.github.io/WebAppsDocs/en-US/win10/CreateHWA.htm)
|
||||
that wraps ``codethemicrobit.com`` and provides additional features.
|
||||
|
||||
### Building
|
||||
|
||||
* Install Visual Studio 2015 Update 2 or higher. Make sure the Windows 10 templates are installed.
|
||||
|
@ -28,7 +28,7 @@ export function deployCoreAsync(res: ts.pxtc.CompileResult) {
|
||||
|
||||
function getBitDrivesAsync(): Promise<string[]> {
|
||||
if (process.platform == "win32") {
|
||||
let rx = new RegExp("^([A-Z]:).* " + pxt.appTarget.compile.deployDrives)
|
||||
const rx = new RegExp("^([A-Z]:).* " + pxt.appTarget.compile.deployDrives)
|
||||
return execAsync("wmic PATH Win32_LogicalDisk get DeviceID, VolumeName, FileSystem")
|
||||
.then(buf => {
|
||||
let res: string[] = []
|
||||
@ -42,9 +42,14 @@ function getBitDrivesAsync(): Promise<string[]> {
|
||||
})
|
||||
}
|
||||
else if (process.platform == "darwin") {
|
||||
let rx = new RegExp(pxt.appTarget.compile.deployDrives)
|
||||
const rx = new RegExp(pxt.appTarget.compile.deployDrives)
|
||||
return readDirAsync("/Volumes")
|
||||
.then(lst => lst.filter(s => rx.test(s)).map(s => "/Volumes/" + s + "/"))
|
||||
} else if (process.platform == "linux") {
|
||||
const rx = new RegExp(pxt.appTarget.compile.deployDrives)
|
||||
const user = process.env["USER"]
|
||||
return readDirAsync(`/media/${user}`)
|
||||
.then(lst => lst.filter(s => rx.test(s)).map(s => `/media/${user}/${s}/`))
|
||||
} else {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
@ -19,10 +19,3 @@
|
||||
<link rel="shortcut icon" href="@appLogo@">
|
||||
<meta name="theme-color" content="@accentColor@">
|
||||
|
||||
<style>
|
||||
#root .avatar .avatar-image {
|
||||
background-image: url(https://az851932.vo.msecnd.net/pub/jovrytni/microbit.simplified.svg);
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
13
docfiles/tracking.html
Normal file
@ -0,0 +1,13 @@
|
||||
<script type="text/javascript">
|
||||
var appInsights=window.appInsights||function(config){
|
||||
function r(config){t[config]=function(){var i=arguments;t.queue.push(function(){t[config].apply(t,i)})}}var t={config:config},u=document,e=window,o="script",s=u.createElement(o),i,f;for(s.src=config.url||"//az416426.vo.msecnd.net/scripts/a/ai.0.js",u.getElementsByTagName(o)[0].parentNode.appendChild(s),t.cookie=u.cookie,t.queue=[],i=["Event","Exception","Metric","PageView","Trace"];i.length;)r("track"+i.pop());return r("setAuthenticatedUserContext"),r("clearAuthenticatedUserContext"),config.disableExceptionTracking||(i="onerror",r("_"+i),f=e[i],e[i]=function(config,r,u,e,o){var s=f&&f(config,r,u,e,o);return s!==!0&&t["_"+i](config,r,u,e,o),s}),t
|
||||
}({
|
||||
instrumentationKey:"e9ae05ca-350b-427a-9775-3ba3f6efabce"
|
||||
});window.appInsights=appInsights;
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
|
||||
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
|
||||
|
||||
mixpanel.init("762fef19c053a0ea4cec43d2fecae76e");
|
||||
</script>
|
@ -1,19 +1,24 @@
|
||||
# Run Scripts on your micro:bit
|
||||
# Running programs on your micro:bit
|
||||
|
||||
How to compile, transfer, and run a script on your micro:bit.
|
||||
How to compile, transfer, and run a program on your micro:bit.
|
||||
|
||||
While you're writing and testing your scripts, you'll mostly be running scripts in your browser by clicking the `PLay` button
|
||||
(see [run code in your browser](/device/simulator) for info about this).
|
||||
While you're writing and testing your programs, you'll mostly be [running them
|
||||
in the simulator](/device/simulator), but once you've finished your program you
|
||||
can **compile** it and run it on your micro:bit.
|
||||
|
||||
Once your masterpiece is complete, you can compile your script and run it on your micro:bit.
|
||||
The basic steps are:
|
||||
|
||||
1. Connect your micro:bit to your computer via USB
|
||||
2. Click **Download** and download the `.hex` file
|
||||
3. Copy the `.hex` file from your computer onto the micro:bit drive
|
||||
|
||||
## Requirements
|
||||
|
||||
You need the following things to transfer and run a script on your micro:bit:
|
||||
|
||||
* A-Male to Micro USB cable to connect your computer to your micro:bit. This is the same cable that is commonly used to connect a smart phone to a computer.
|
||||
* a PC running Windows 7 of later, or a Mac running OS X 10.6 or later
|
||||
* access to the Internet
|
||||
* A-Male to Micro USB cable to connect your computer to your micro:bit. This is
|
||||
the same cable that is commonly used to connect a smart phone to a computer.
|
||||
* A PC running Windows 7 or later, or a Mac running OS X 10.6 or later
|
||||
|
||||
## Step 1: Connect your micro:bit to your computer
|
||||
|
||||
@ -23,108 +28,151 @@ First, connect the micro:bit:
|
||||
|
||||
2. Connect the other end of the USB cable to a USB port on your computer.
|
||||
|
||||
Your computer should recognise your micro:bit as a new drive. On computers running Windows, MICROBIT appears as a drive under Devices and drives. On a Mac it appears as a new drive under Devices.
|
||||
Your computer should recognise your micro:bit as a new drive. On computers
|
||||
running Windows, `MICROBIT` appears as a drive under Devices and drives. On a Mac
|
||||
it appears as a new drive under Devices.
|
||||
|
||||
Windows
|
||||
**Windows**
|
||||
|
||||

|
||||

|
||||
|
||||
Mac (picture bvabdbco)
|
||||
WARN: unknown picture: bvabdbco:5x3
|
||||
**Mac**
|
||||
|
||||
## Step 2: Compile your script
|
||||

|
||||
|
||||
Next, compile your script:
|
||||
## Step 2: Download your program
|
||||
|
||||
1. Sign in to Touch Develop on your computer.
|
||||
|
||||
2. Open your script (find the script in **My Scripts** and click `Edit`).
|
||||
|
||||
3. Click **Download**. Your script is converted into a hex file that you can transfer and run on your micro:bit.
|
||||
|
||||
4. When prompted, choose to save the compiled file on your computer (or anywhere other than the micro:bit). Depending on which browser you are using, the download will adopt the download behaviour of that particular browser.
|
||||
1. Open your project on [codethemicrobit.com](https://codethemicrobit.com)
|
||||
2. Click **Download**
|
||||
3. When prompted, choose to **save** the compiled file onto your computer. The
|
||||
prompt will be different depending on which browser you are using, or
|
||||
whether you are using a Windows computer or a Mac
|
||||
|
||||
### Windows
|
||||
|
||||
** Chrome**
|
||||
#### Chrome
|
||||
|
||||
Your .hex file appears as a download at the bottom of the browser. Open up your windows file explorer. Your micro:bit appears as a drive called MICROBIT.
|
||||
Your `.hex` file appears as a download at the bottom of the browser. Click on
|
||||
the arrow next to the name of the file and then click **Show in folder**.
|
||||
|
||||
**Right click** on the download and select **show in folder**. Drag and drop the hex file from the download folder onto the MICROBIT drive.
|
||||

|
||||
|
||||
Alternatively, you can drag and drop the downloaded hex file from the bottom of the browser onto the file explorer and onto the MICROBIT drive.
|
||||
Drag and drop the `.hex` file from the download folder onto the `MICROBIT` drive.
|
||||
|
||||

|
||||
#### Firefox
|
||||
|
||||
**Firefox**
|
||||
A window will appear asking whether you want to save or open the `.hex` file.
|
||||
Select **Save File** and then select **OK**.
|
||||
|
||||
A dialogue box will appear, asking whether you would like to open or save your hex file. Select **Save**, then **OK** and the file will appear in your downloads in the top right of your browser. Select the **blue arrow**, select the relevant file and drag and drop it onto your Windows Explorer and onto your MICROBIT drive.
|
||||

|
||||
|
||||

|
||||
The file will then appear in your downloads in the top right of your browser.
|
||||
Click the **folder icon** next to the filename to open it in Windows Explorer.
|
||||
|
||||

|
||||

|
||||
|
||||
**IE10**
|
||||
Drag and drop the `.hex` file from the download folder onto the `MICROBIT` drive.
|
||||
|
||||
Click on **Download**. You will see a message “Do you want to save this .hex file.” Select **Save**.
|
||||
#### Microsoft Edge
|
||||
|
||||
A message will appear at the bottom of the browser asking what you want to do
|
||||
with the file. Click **Save**:
|
||||
|
||||

|
||||
|
||||
Then click **Open folder** and drag and drop the file from your Downloads to
|
||||
your `MICROBIT` drive.
|
||||
|
||||

|
||||
|
||||
#### Internet Explorer
|
||||
|
||||
A message will appear at the bottom of the browser asking what you want to do
|
||||
with the file. Click **Save**:
|
||||
|
||||

|
||||
|
||||
Then click **Open folder** and drag and drop the file from your Downloads to
|
||||
your `MICROBIT` drive.
|
||||
|
||||

|
||||
|
||||
### Mac
|
||||
|
||||
** Safari**
|
||||
#### Safari
|
||||
|
||||
When you select **Download** in Safari on Mac, your file will be downloaded to your downloads folder. Go to your downloads folder and open the file. In Safari the file will appear as unknown.txt rather than a named .hex file. Drag and drop it onto your MICROBIT drive.
|
||||
When you select **Download** in Safari a file called `Unknown` will be
|
||||
downloaded into your Downloads folder. Open your Downloads folder and drag and
|
||||
drop the file onto your `MICROBIT` drive, under Devices:
|
||||
|
||||

|
||||

|
||||
|
||||
**Firefox**
|
||||
#### Firefox
|
||||
|
||||
A dialogue box will appear, asking whether you would like to open or save your hex file. Select **Save** and **OK** and the file will then appear in your downloads in the top right of your browser. Click on **Show in Finder** and the file will appear in your downloads folder. Select the file and drag and drop it onto your MICROBIT drive.
|
||||
A dialogue box will appear, asking whether you would like to open or save your
|
||||
hex file. Select **Save file** and click **OK** and the file will then appear in
|
||||
your downloads in the top right of your browser. Right click on the file and
|
||||
click on **Show in Finder** and the file will appear in your downloads folder.
|
||||
Select the file and drag and drop it onto your `MICROBIT` drive.
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
**Chrome**
|
||||
#### Chrome
|
||||
|
||||
When you select **Download** in Chrome, the file will be downloaded to the bottom of the browser in .hex format. Click on the small arrow and select **Show in Finder**. This will show the file in your download folder. Drag and drop the file onto your MICROBIT drive.
|
||||
When you select **Download** in Chrome, the file will appear at the bottom of
|
||||
the browser. Click on the small arrow and select **Show in Finder**. This will
|
||||
show the file in your download folder. Drag and drop the file onto your
|
||||
`MICROBIT` drive.
|
||||
|
||||

|
||||

|
||||
|
||||
## Step 3: Transfer the file to your micro:bit
|
||||
|
||||
1. The file will transfer onto your micro:bit.
|
||||
|
||||
2. If you're using Windows, you can use **Send to** as described below.
|
||||
|
||||
3. The LED on the back of your micro:bit flashes during the transfer (which should only take a few seconds).
|
||||
|
||||
4. Once transferred, the code will run automatically on your micro:bit. To rerun your program, press the reset button on the back of your micro:bit. The reset button automatically runs the newest file on the micro:bit.
|
||||
* Once you've found the folder containing your `.hex` file, drag and drop it
|
||||
onto your `MICROBIT` drive
|
||||
* If you're using Windows, you can use **Send to** as described below
|
||||
* The LED on the back of your micro:bit flashes during the transfer (which
|
||||
should only take a few seconds).
|
||||
* Once transferred, the code will run automatically on your micro:bit. To rerun
|
||||
your program, press the reset button on the back of your micro:bit. The reset
|
||||
button automatically runs the newest file on the micro:bit.
|
||||
|
||||
**Send to**: If you're using Windows you use *Send to* in File Explorer:
|
||||
|
||||
- In File Explorer, right-click on the hex file (created in Step 2 above), choose **Send to**, and then **MICROBIT**.
|
||||
|
||||

|
||||

|
||||
|
||||
By copying the script onto the 'MICROBIT' drive, you have programmed it into the flash memory on the micro:bit, which means even after you unplug the micro:bit, your script will still run if the micro:bit is powered by battery.
|
||||
By copying the script onto the `MICROBIT` drive, you have programmed it into the
|
||||
flash memory on the micro:bit, which means even after you unplug the micro:bit,
|
||||
your program will still run if the micro:bit is powered by battery.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
You can’t drag and drop more than one hex file at once onto your micro:bit. If you try to drag and drop a second hex file onto your micro:bit before the first file has finished downloading, then the second file may fail in different ways.
|
||||
You can’t drag and drop more than one hex file at once onto your micro:bit. If
|
||||
you try to drag and drop a second hex file onto your micro:bit before the first
|
||||
file has finished downloading, then the second file may fail in different ways.
|
||||
|
||||
When the first program has been written to the micro:bit, the drive will disengage. If you drag and drop a second file at this point it may not find the drive and the second write will fail.
|
||||
When the first program has been written to the micro:bit, the drive will
|
||||
disengage. If you drag and drop a second file at this point it may not find the
|
||||
drive and the second write will fail.
|
||||
|
||||
The errors may look like this:
|
||||
|
||||
**Windows**
|
||||
|
||||

|
||||

|
||||
|
||||
**Mac**
|
||||
|
||||

|
||||

|
||||
|
||||
Or it may appear that there are two hex files on your micro:bit so the micro:bit won’t be able to run multiple files. To rectify this, unplug your micro:bit and plug it in again. Make sure that your micro:bit appears as MICROBIT and not MAINTENANCE.
|
||||
Or it may appear that there are two hex files on your micro:bit so the micro:bit
|
||||
won’t be able to run multiple files. To rectify this, unplug your micro:bit and
|
||||
plug it in again. Make sure that your micro:bit appears as `MICROBIT` and not
|
||||
`MAINTENANCE`.
|
||||
|
||||
### See also
|
||||
|
||||
|
@ -58,7 +58,9 @@ After running this simulation several seconds by moving the micro:bit side to si
|
||||

|
||||
|
||||
### ~
|
||||
Finally, you must open the Excel CSV file by clicking on the data.xls file that was downloaded to Downloads Folder.
|
||||
|
||||
Finally, you must open the Excel CSV file by clicking on the `data.csv` file
|
||||
that was downloaded to Downloads Folder.
|
||||
|
||||

|
||||
|
||||
@ -93,4 +95,4 @@ NEXT: The Watch
|
||||
|
||||
```package
|
||||
microbit-radio
|
||||
```
|
||||
```
|
||||
|
29
docs/raspberry-pi.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Raspberry Pi and Raspbian
|
||||
|
||||
It is possible to run the web editor or [command line interface](/cli) from Raspbian on Raspberry Pi 2 or 3.
|
||||
|
||||
## Web editor
|
||||
|
||||
The web editor requires to install IceWeasel (Firefox) as the built-in browser cannot handle it.
|
||||
|
||||
```
|
||||
sudo apt-get install iceweasel
|
||||
```
|
||||
|
||||
Once installed simply navigate to https://codethemicrobit.com or type
|
||||
|
||||
```
|
||||
firefox https://codethemicrobit.com
|
||||
```
|
||||
|
||||
## Command line
|
||||
|
||||
The PXT command line also works on Raspbian and allows to run a local server and/or edit programs from any text editor.
|
||||
|
||||
* Node.JS 6.0 needs installed
|
||||
|
||||
To install all the tools,
|
||||
|
||||
```
|
||||
curl -s https://raw.githubusercontent.com/Microsoft/pxt-rpi/master/install.sh | sh -
|
||||
```
|
BIN
docs/static/mb/device/pins-0.png
vendored
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 77 KiB |
BIN
docs/static/mb/device/usb-1.jpg
vendored
Before Width: | Height: | Size: 391 KiB |
BIN
docs/static/mb/device/usb-10.png
vendored
Before Width: | Height: | Size: 25 KiB |
BIN
docs/static/mb/device/usb-2.jpg
vendored
Before Width: | Height: | Size: 289 KiB |
BIN
docs/static/mb/device/usb-3.jpg
vendored
Before Width: | Height: | Size: 385 KiB |
BIN
docs/static/mb/device/usb-4.jpg
vendored
Before Width: | Height: | Size: 180 KiB |
BIN
docs/static/mb/device/usb-5.jpg
vendored
Before Width: | Height: | Size: 121 KiB |
BIN
docs/static/mb/device/usb-6.jpg
vendored
Before Width: | Height: | Size: 336 KiB |
BIN
docs/static/mb/device/usb-7.jpg
vendored
Before Width: | Height: | Size: 324 KiB |
BIN
docs/static/mb/device/usb-osx-chrome.png
vendored
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
docs/static/mb/device/usb-osx-copy-file-error.png
vendored
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
docs/static/mb/device/usb-osx-device.png
vendored
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
docs/static/mb/device/usb-osx-dnd.png
vendored
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
docs/static/mb/device/usb-osx-firefox-1.png
vendored
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
docs/static/mb/device/usb-osx-firefox-2.png
vendored
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
docs/static/mb/device/usb-windows-chrome.png
vendored
Normal file
After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
BIN
docs/static/mb/device/usb-windows-edge-1.png
vendored
Normal file
After Width: | Height: | Size: 156 KiB |
BIN
docs/static/mb/device/usb-windows-edge-2.png
vendored
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
docs/static/mb/device/usb-windows-firefox-1.png
vendored
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
docs/static/mb/device/usb-windows-firefox-2.png
vendored
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
docs/static/mb/device/usb-windows-ie11-1.png
vendored
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
docs/static/mb/device/usb-windows-ie11-2.png
vendored
Normal file
After Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
BIN
docs/static/mb/js/basicFuns.png
vendored
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 15 KiB |
BIN
docs/static/mb/js/basicIntell.png
vendored
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 22 KiB |
BIN
docs/static/mb/js/forIntell.png
vendored
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 7.9 KiB |
@ -1,5 +1,4 @@
|
||||
{
|
||||
"bluetooth": "Support for additional Bluetooth services.",
|
||||
"bluetooth.onBluetoothConnected": "Register code to run when the micro:bit is connected to over Bluetooth",
|
||||
"bluetooth.onBluetoothConnected|param|body": "Code to run when a Bluetooth connection is established",
|
||||
"bluetooth.onBluetoothDisconnected": "Register code to run when a bluetooth connection to the micro:bit is lost",
|
||||
@ -10,6 +9,7 @@
|
||||
"bluetooth.startLEDService": "Starts the Bluetooth LED service",
|
||||
"bluetooth.startMagnetometerService": "Starts the Bluetooth magnetometer service",
|
||||
"bluetooth.startTemperatureService": "Starts the Bluetooth temperature service",
|
||||
"bluetooth.startUartService": "Starts the Bluetooth UART service",
|
||||
"bluetooth.uartRead": "Reads from the Bluetooth UART service buffer, returning its contents when the specified delimiter character is encountered.",
|
||||
"bluetooth.uartWrite": "Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device."
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
"bluetooth.startLEDService|block": "bluetooth led service",
|
||||
"bluetooth.startMagnetometerService|block": "bluetooth magnetometer service",
|
||||
"bluetooth.startTemperatureService|block": "bluetooth temperature service",
|
||||
"bluetooth.startUartService|block": "bluetooth uart service",
|
||||
"bluetooth.uartRead|block": "bluetooth uart read %del=bluetooth_uart_delimiter_conv",
|
||||
"bluetooth.uartWrite|block": "bluetooth uart write %data",
|
||||
"bluetooth|block": "bluetooth"
|
||||
|
@ -101,38 +101,18 @@ namespace bluetooth {
|
||||
uart = new MicroBitUARTService(*uBit.ble, 61, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device.
|
||||
*/
|
||||
//% help=bluetooth/uart-write
|
||||
//% blockId=bluetooth_uart_write block="bluetooth uart write %data" blockGap=8
|
||||
//% parts="bluetooth"
|
||||
//%
|
||||
void uartWrite(StringData *data) {
|
||||
startUartService();
|
||||
uart->send(ManagedString(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the Bluetooth UART service buffer, returning its contents when the specified delimiter character is encountered.
|
||||
*/
|
||||
//% help=bluetooth/uart-read
|
||||
//% blockId=bluetooth_uart_read block="bluetooth uart read %del=bluetooth_uart_delimiter_conv" blockGap=8
|
||||
//% parts="bluetooth"
|
||||
//%
|
||||
StringData* uartRead(StringData *del) {
|
||||
startUartService();
|
||||
return uart->readUntil(ManagedString(del)).leakData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delimiter corresponding string
|
||||
*/
|
||||
//% blockId="bluetooth_uart_delimiter_conv" block="%del"
|
||||
//% weight=1
|
||||
//% parts="bluetooth"
|
||||
StringData* delimiters(Delimiters del) {
|
||||
ManagedString c("\n\n,$:.#"[max(0, min(6, (int)del))]);
|
||||
return c.leakData();
|
||||
}
|
||||
/**
|
||||
* Register code to run when the micro:bit is connected to over Bluetooth
|
||||
* @param body Code to run when a Bluetooth connection is established
|
||||
|
43
libs/microbit-bluetooth/bluetooth.ts
Normal file
@ -0,0 +1,43 @@
|
||||
namespace bluetooth {
|
||||
/**
|
||||
* Returns the delimiter corresponding string
|
||||
*/
|
||||
//% blockId="bluetooth_uart_delimiter_conv" block="%del"
|
||||
//% weight=1 parts="bluetooth"
|
||||
export function delimiters(del: Delimiters): string {
|
||||
// even though it might not look like, this is more
|
||||
// (memory) efficient than the C++ implementation, because the
|
||||
// strings are statically allocated and take no RAM
|
||||
switch (del) {
|
||||
case Delimiters.NewLine: return "\n"
|
||||
case Delimiters.Comma: return ","
|
||||
case Delimiters.Dollar: return "$"
|
||||
case Delimiters.Colon: return ":"
|
||||
case Delimiters.Fullstop: return "."
|
||||
case Delimiters.Hash: return "#"
|
||||
default: return "\n"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device.
|
||||
*/
|
||||
//% help=bluetooth/uart-write
|
||||
//% blockId=bluetooth_uart_write block="bluetooth uart write %data" blockGap=8
|
||||
//% parts="bluetooth" shim=bluetooth::uartWrite
|
||||
export function uartWrite(data: string): void {
|
||||
// dummy implementation for simulator
|
||||
console.log("UART Write: " + data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the Bluetooth UART service buffer, returning its contents when the specified delimiter character is encountered.
|
||||
*/
|
||||
//% help=bluetooth/uart-read
|
||||
//% blockId=bluetooth_uart_read block="bluetooth uart read %del=bluetooth_uart_delimiter_conv" blockGap=8
|
||||
//% parts="bluetooth" shim=bluetooth::uartRead
|
||||
export function uartRead(del: string): string {
|
||||
// dummy implementation for simulator
|
||||
return "???"
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
"README.md",
|
||||
"enums.d.ts",
|
||||
"shims.d.ts",
|
||||
"bluetooth.ts",
|
||||
"bluetooth.cpp"
|
||||
],
|
||||
"public": true,
|
||||
|
24
libs/microbit-bluetooth/shims.d.ts
vendored
@ -63,30 +63,6 @@ declare namespace bluetooth {
|
||||
//% parts="bluetooth" shim=bluetooth::startUartService
|
||||
function startUartService(): void;
|
||||
|
||||
/**
|
||||
* Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device.
|
||||
*/
|
||||
//% help=bluetooth/uart-write
|
||||
//% blockId=bluetooth_uart_write block="bluetooth uart write %data" blockGap=8
|
||||
//% parts="bluetooth" shim=bluetooth::uartWrite
|
||||
function uartWrite(data: string): void;
|
||||
|
||||
/**
|
||||
* Reads from the Bluetooth UART service buffer, returning its contents when the specified delimiter character is encountered.
|
||||
*/
|
||||
//% help=bluetooth/uart-read
|
||||
//% blockId=bluetooth_uart_read block="bluetooth uart read %del=bluetooth_uart_delimiter_conv" blockGap=8
|
||||
//% parts="bluetooth" shim=bluetooth::uartRead
|
||||
function uartRead(del: string): string;
|
||||
|
||||
/**
|
||||
* Returns the delimiter corresponding string
|
||||
*/
|
||||
//% blockId="bluetooth_uart_delimiter_conv" block="%del"
|
||||
//% weight=1
|
||||
//% parts="bluetooth" shim=bluetooth::delimiters
|
||||
function delimiters(del: Delimiters): string;
|
||||
|
||||
/**
|
||||
* Register code to run when the micro:bit is connected to over Bluetooth
|
||||
* @param body Code to run when a Bluetooth connection is established
|
||||
|
@ -22,6 +22,8 @@
|
||||
"control": "Runtime and event utilities.",
|
||||
"control.inBackground": "Schedules code that run in the background.",
|
||||
"control.reset": "Resets the BBC micro:bit.",
|
||||
"control.waitMicros": "Blocks the current fiber for the given microseconds",
|
||||
"control.waitMicros|param|micros": "number of micro-seconds to wait. eg: 4",
|
||||
"game": "A single-LED sprite game engine",
|
||||
"game.addScore": "Adds points to the current score",
|
||||
"game.addScore|param|points": "amount of points to change, eg: 1",
|
||||
@ -52,9 +54,12 @@
|
||||
"input.onLogoDown|param|body": "TODO",
|
||||
"input.onLogoUp": "Attaches code to run when the logo is oriented upwards and the board is vertical.",
|
||||
"input.onLogoUp|param|body": "TODO",
|
||||
"input.onPinPressed": "Do something when a pin(``P0``, ``P1`` or both ``P2``) is pressed.",
|
||||
"input.onPinPressed|param|body": "TODO",
|
||||
"input.onPinPressed|param|name": "TODO",
|
||||
"input.onPinPressed": "Do something when a pin is pressed.",
|
||||
"input.onPinPressed|param|body": "the code to run when the pin is pressed",
|
||||
"input.onPinPressed|param|name": "the pin that needs to be pressed",
|
||||
"input.onPinReleased": "Do something when a pin is released.",
|
||||
"input.onPinReleased|param|body": "the code to run when the pin is released",
|
||||
"input.onPinReleased|param|name": "the pin that needs to be released",
|
||||
"input.onScreenDown": "Attaches code to run when the screen is facing down.",
|
||||
"input.onScreenDown|param|body": "TODO",
|
||||
"input.onScreenUp": "Attaches code to run when the screen is facing up.",
|
||||
@ -152,6 +157,8 @@
|
||||
"pins.setPull": "Configures the pull of this pin.",
|
||||
"pins.setPull|param|name": "pin to set the pull mode on",
|
||||
"pins.setPull|param|pull": "one of the mbed pull configurations: PullUp, PullDown, PullNone ",
|
||||
"pins.spiWrite": "Write to the SPI slave and return the response",
|
||||
"pins.spiWrite|param|value": "Data to be sent to the SPI slave",
|
||||
"serial": "Reading and writing data over a serial connection.",
|
||||
"serial.readLine": "Reads a line of text from the serial port.",
|
||||
"serial.redirect": "Dynamically configuring the serial instance to use pins other than USBTX and USBRX.",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"basic|block": "basic",
|
||||
"control.inBackground|block": "run in background",
|
||||
"control.reset|block": "reset",
|
||||
"control.waitMicros|block": "wait (µs)%micros",
|
||||
"control|block": "control",
|
||||
"game.addScore|block": "change score by|%points",
|
||||
"game.gameOver|block": "game over",
|
||||
@ -28,7 +29,8 @@
|
||||
"input.magneticForce|block": "magnetic force (µT)|%NAME",
|
||||
"input.onButtonPressed|block": "on button|%NAME|pressed",
|
||||
"input.onGesture|block": "on |%NAME",
|
||||
"input.onPinPressed|block": "on pin|%NAME|pressed",
|
||||
"input.onPinPressed|block": "on pin %NAME|pressed",
|
||||
"input.onPinReleased|block": "on pin %NAME|released",
|
||||
"input.pinIsPressed|block": "pin %NAME|is pressed",
|
||||
"input.rotation|block": "rotation (°)|%NAME",
|
||||
"input.runningTime|block": "running time (ms)",
|
||||
@ -41,6 +43,7 @@
|
||||
"led.point|block": "point|x %x|y %y",
|
||||
"led.setBrightness|block": "set brightness %value",
|
||||
"led.stopAnimation|block": "stop animation",
|
||||
"led.toggle|block": "toggle|x %x|y %y",
|
||||
"led.unplot|block": "unplot|x %x|y %y",
|
||||
"led|block": "led",
|
||||
"music.beat|block": "%fraction|beat",
|
||||
@ -65,6 +68,7 @@
|
||||
"pins.servoSetPulse|block": "servo set pulse|pin %value|to (µs) %micros",
|
||||
"pins.servoWritePin|block": "servo write|pin %name|to %value",
|
||||
"pins.setPull|block": "set pull|pin %pin|to %pull",
|
||||
"pins.spiWrite|block": "spi write %value",
|
||||
"pins|block": "pins",
|
||||
"serial.readLine|block": "serial read line",
|
||||
"serial.redirect|block": "serial redirect to|TX %tx|RX %rx|at baud rate %rate",
|
||||
|
@ -37,4 +37,10 @@ namespace control {
|
||||
panic(98)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display warning in the simulator.
|
||||
*/
|
||||
//% shim=pxtrt::runtimeWarning
|
||||
export function runtimeWarning(message: string) { }
|
||||
}
|
||||
|
@ -166,6 +166,7 @@ namespace Array_ {
|
||||
int removeElement(RefCollection *c, uint32_t x) { return c->removeElement(x); }
|
||||
}
|
||||
|
||||
|
||||
// Import some stuff directly
|
||||
namespace pxt {
|
||||
//%
|
||||
@ -181,10 +182,12 @@ namespace pxt {
|
||||
//%
|
||||
Action mkAction(int reflen, int totallen, int startptr);
|
||||
//%
|
||||
RefRecord* mkRecord(int reflen, int totallen);
|
||||
//%
|
||||
RefRecord* mkClassInstance(int offset);
|
||||
//%
|
||||
void RefRecord_destroy(RefRecord *r);
|
||||
//%
|
||||
void RefRecord_print(RefRecord *r);
|
||||
//%
|
||||
void debugMemLeaks();
|
||||
//%
|
||||
int incr(uint32_t e);
|
||||
@ -308,6 +311,72 @@ namespace pxtrt {
|
||||
}
|
||||
}
|
||||
|
||||
//%
|
||||
RefMap *mkMap() {
|
||||
return new RefMap();
|
||||
}
|
||||
|
||||
//%
|
||||
uint32_t mapGet(RefMap *map, uint32_t key) {
|
||||
int i = map->findIdx(key);
|
||||
if (i < 0) {
|
||||
map->unref();
|
||||
return 0;
|
||||
}
|
||||
uint32_t r = map->data[i].val;
|
||||
map->unref();
|
||||
return r;
|
||||
}
|
||||
|
||||
//%
|
||||
uint32_t mapGetRef(RefMap *map, uint32_t key) {
|
||||
int i = map->findIdx(key);
|
||||
if (i < 0) {
|
||||
map->unref();
|
||||
return 0;
|
||||
}
|
||||
uint32_t r = incr(map->data[i].val);
|
||||
map->unref();
|
||||
return r;
|
||||
}
|
||||
|
||||
//%
|
||||
void mapSet(RefMap *map, uint32_t key, uint32_t val) {
|
||||
int i = map->findIdx(key);
|
||||
if (i < 0) {
|
||||
map->data.push_back({
|
||||
key << 1,
|
||||
val
|
||||
});
|
||||
} else {
|
||||
if (map->data[i].key & 1) {
|
||||
decr(map->data[i].val);
|
||||
map->data[i].key = key << 1;
|
||||
}
|
||||
map->data[i].val = val;
|
||||
}
|
||||
map->unref();
|
||||
}
|
||||
|
||||
//%
|
||||
void mapSetRef(RefMap *map, uint32_t key, uint32_t val) {
|
||||
int i = map->findIdx(key);
|
||||
if (i < 0) {
|
||||
map->data.push_back({
|
||||
(key << 1) | 1,
|
||||
val
|
||||
});
|
||||
} else {
|
||||
if (map->data[i].key & 1) {
|
||||
decr(map->data[i].val);
|
||||
} else {
|
||||
map->data[i].key = (key << 1) | 1;
|
||||
}
|
||||
map->data[i].val = val;
|
||||
}
|
||||
map->unref();
|
||||
}
|
||||
|
||||
//
|
||||
// Debugger
|
||||
//
|
||||
@ -316,4 +385,9 @@ namespace pxtrt {
|
||||
void* getGlobalsPtr() {
|
||||
return globals;
|
||||
}
|
||||
|
||||
//%
|
||||
void runtimeWarning(StringData *s) {
|
||||
// noop for now
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@ -27,6 +27,8 @@
|
||||
"serial.cpp",
|
||||
"serial.ts",
|
||||
"buffer.cpp",
|
||||
"pxtparts.json",
|
||||
"parts/speaker.svg",
|
||||
"_locales/fr/microbit-jsdoc-strings.json"
|
||||
],
|
||||
"public": true,
|
||||
|
126
libs/microbit/pxtparts.json
Normal file
@ -0,0 +1,126 @@
|
||||
{
|
||||
"buttonpair": {
|
||||
"simulationBehavior": "buttonpair",
|
||||
"visual": {
|
||||
"builtIn": "buttonpair",
|
||||
"width": 75,
|
||||
"height": 45,
|
||||
"pinDistance": 15,
|
||||
"pinLocations": [
|
||||
{"x": 0, "y": 0},
|
||||
{"x": 30, "y": 45},
|
||||
{"x": 45, "y": 0},
|
||||
{"x": 75, "y": 45}
|
||||
]
|
||||
},
|
||||
"numberOfPins": 4,
|
||||
"pinDefinitions": [
|
||||
{"target": "P14", "style": "male", "orientation": "-Z"},
|
||||
{"target": "ground", "style": "male", "orientation": "-Z"},
|
||||
{"target": "P15", "style": "male", "orientation": "-Z"},
|
||||
{"target": "ground", "style": "male", "orientation": "-Z"}
|
||||
],
|
||||
"instantiation": {
|
||||
"kind": "singleton"
|
||||
},
|
||||
"assembly": [
|
||||
{"part": true},
|
||||
{"pinIndices": [0, 1]},
|
||||
{"pinIndices": [2, 3]}
|
||||
]
|
||||
},
|
||||
"neopixel": {
|
||||
"simulationBehavior": "neopixel",
|
||||
"visual": {
|
||||
"builtIn": "neopixel",
|
||||
"width": 58,
|
||||
"height": 113,
|
||||
"pinDistance": 9,
|
||||
"pinLocations": [
|
||||
{"x": 10, "y": 0},
|
||||
{"x": 19, "y": 0},
|
||||
{"x": 28, "y": 0}
|
||||
]
|
||||
},
|
||||
"numberOfPins": 3,
|
||||
"pinDefinitions": [
|
||||
{"target": {"pinInstantiationIdx": 0}, "style": "solder", "orientation": "+Z"},
|
||||
{"target": "threeVolt", "style": "solder", "orientation": "+Z"},
|
||||
{"target": "ground", "style": "solder", "orientation": "+Z"}
|
||||
],
|
||||
"instantiation": {
|
||||
"kind": "function",
|
||||
"fullyQualifiedName": "neopixel.create",
|
||||
"argumentRoles": [
|
||||
{"pinInstantiationIdx": 0, "partParameter": "pin"},
|
||||
{"partParameter": "mode"}
|
||||
]
|
||||
},
|
||||
"assembly": [
|
||||
{"part": true, "pinIndices": [2]},
|
||||
{"pinIndices": [0, 1]}
|
||||
]
|
||||
},
|
||||
"ledmatrix": {
|
||||
"visual": {
|
||||
"builtIn": "ledmatrix",
|
||||
"width": 105,
|
||||
"height": 105,
|
||||
"pinDistance": 15,
|
||||
"pinLocations": [
|
||||
{"x": 0, "y": 0},
|
||||
{"x": 15, "y": 0},
|
||||
{"x": 30, "y": 0},
|
||||
{"x": 45, "y": 0},
|
||||
{"x": 105, "y": 105},
|
||||
{"x": 0, "y": 105},
|
||||
{"x": 15, "y": 105},
|
||||
{"x": 30, "y": 105},
|
||||
{"x": 45, "y": 105},
|
||||
{"x": 60, "y": 0}
|
||||
]
|
||||
},
|
||||
"simulationBehavior": "ledmatrix",
|
||||
"numberOfPins": 10,
|
||||
"instantiation": {"kind": "singleton"},
|
||||
"pinDefinitions": [
|
||||
{"target": "P6", "style": "male", "orientation": "-Z", "colorGroup": 0},
|
||||
{"target": "P7", "style": "male", "orientation": "-Z", "colorGroup": 0},
|
||||
{"target": "P8", "style": "male", "orientation": "-Z", "colorGroup": 0},
|
||||
{"target": "P9", "style": "male", "orientation": "-Z", "colorGroup": 0},
|
||||
{"target": "P10", "style": "male", "orientation": "-Z", "colorGroup": 0},
|
||||
{"target": "P12", "style": "male", "orientation": "-Z", "colorGroup": 1},
|
||||
{"target": "P13", "style": "male", "orientation": "-Z", "colorGroup": 1},
|
||||
{"target": "P16", "style": "male", "orientation": "-Z", "colorGroup": 1},
|
||||
{"target": "P19", "style": "male", "orientation": "-Z", "colorGroup": 1},
|
||||
{"target": "P20", "style": "male", "orientation": "-Z", "colorGroup": 1}
|
||||
],
|
||||
"assembly": [
|
||||
{"part": true},
|
||||
{"pinIndices": [0, 1, 2, 3, 4]},
|
||||
{"pinIndices": [5, 6, 7, 8, 9]}
|
||||
]
|
||||
},
|
||||
"speaker": {
|
||||
"numberOfPins": 2,
|
||||
"visual": {
|
||||
"image": "/parts/speaker.svg",
|
||||
"width": 500,
|
||||
"height": 500,
|
||||
"pinDistance": 70,
|
||||
"pinLocations": [
|
||||
{"x": 180, "y": 135},
|
||||
{"x": 320, "y": 135}
|
||||
]
|
||||
},
|
||||
"pinDefinitions": [
|
||||
{"target": "P0", "style": "male", "orientation": "-Z"},
|
||||
{"target": "ground", "style": "male", "orientation": "-Z"}
|
||||
],
|
||||
"instantiation": {"kind": "singleton"},
|
||||
"assembly": [
|
||||
{"part": true, "pinIndices": [0]},
|
||||
{"pinIndices": [1]}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pxt-microbit",
|
||||
"version": "0.3.67",
|
||||
"version": "0.4.1",
|
||||
"description": "micro:bit target for PXT",
|
||||
"keywords": [
|
||||
"JavaScript",
|
||||
@ -29,6 +29,6 @@
|
||||
"typescript": "^1.8.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"pxt-core": "0.3.76"
|
||||
"pxt-core": "0.4.2"
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,9 @@
|
||||
},
|
||||
"simulator": {
|
||||
"autoRun": true,
|
||||
"streams": true,
|
||||
"aspectRatio": 1.22,
|
||||
"parts": true,
|
||||
"partsAspectRatio": 0.69,
|
||||
"builtinParts": {
|
||||
"accelerometer": true,
|
||||
@ -91,7 +93,7 @@
|
||||
"yottaTarget": "bbc-microbit-classic-gcc",
|
||||
"yottaCorePackage": "pxt-microbit-core",
|
||||
"githubCorePackage": "microsoft/pxt-microbit-core",
|
||||
"gittag": "v0.4.2",
|
||||
"gittag": "v0.5.0",
|
||||
"serviceId": "ws"
|
||||
},
|
||||
"serial": {
|
||||
@ -113,6 +115,7 @@
|
||||
"embedUrl": "https://codethemicrobit.com/",
|
||||
"privacyUrl": "https://go.microsoft.com/fwlink/?LinkId=521839",
|
||||
"termsOfUseUrl": "https://go.microsoft.com/fwlink/?LinkID=206977",
|
||||
"githubUrl": "https://github.com/Microsoft/pxt-microbit",
|
||||
"boardName": "BBC micro:bit",
|
||||
"docMenu": [
|
||||
{
|
||||
@ -146,4 +149,4 @@
|
||||
"userVoiceApiKey": "WEkkIGaj1WtJnSUF59iwaA",
|
||||
"userVoiceForumId": 402381
|
||||
}
|
||||
}
|
||||
}
|
||||
|
432
sim/allocator.ts
@ -1,432 +0,0 @@
|
||||
|
||||
namespace pxsim {
|
||||
export interface AllocatorOpts {
|
||||
boardDef: BoardDefinition,
|
||||
cmpDefs: Map<PartDefinition>,
|
||||
fnArgs: any,
|
||||
getBBCoord: (loc: BBRowCol) => visuals.Coord,
|
||||
cmpList: string[]
|
||||
};
|
||||
export interface AllocatorResult {
|
||||
powerWires: WireInst[],
|
||||
components: CmpAndWireInst[]
|
||||
}
|
||||
|
||||
export interface CmpAndWireInst {
|
||||
component: CmpInst,
|
||||
wires: WireInst[]
|
||||
}
|
||||
export interface CmpInst {
|
||||
name: string,
|
||||
breadboardStartColumn: number,
|
||||
breadboardStartRow: string,
|
||||
assemblyStep: number,
|
||||
visual: string | PartVisualDefinition,
|
||||
microbitPins: string[],
|
||||
otherArgs?: string[],
|
||||
}
|
||||
export interface WireInst {
|
||||
start: Loc,
|
||||
end: Loc,
|
||||
color: string,
|
||||
assemblyStep: number
|
||||
};
|
||||
interface PartialCmpAlloc {
|
||||
name: string,
|
||||
def: PartDefinition,
|
||||
pinsAssigned: string[],
|
||||
pinsNeeded: number | number[],
|
||||
breadboardColumnsNeeded: number,
|
||||
otherArgs?: string[],
|
||||
}
|
||||
|
||||
interface AllocLocOpts {
|
||||
nearestBBPin?: BBRowCol,
|
||||
startColumn?: number,
|
||||
cmpGPIOPins?: string[],
|
||||
};
|
||||
interface AllocWireOpts {
|
||||
startColumn: number,
|
||||
cmpGPIOPins: string[],
|
||||
}
|
||||
interface AllocBlock {
|
||||
cmpIdx: number,
|
||||
cmpBlkIdx: number,
|
||||
gpioNeeded: number,
|
||||
gpioAssigned: string[]
|
||||
}
|
||||
function copyDoubleArray(a: string[][]) {
|
||||
return a.map(b => b.map(p => p));
|
||||
}
|
||||
function readPin(arg: string): string {
|
||||
U.assert(!!arg, "Invalid pin: " + arg);
|
||||
let pin = arg.split("DigitalPin.")[1];
|
||||
return pin;
|
||||
}
|
||||
function mkReverseMap(map: {[key: string]: string}) {
|
||||
let origKeys: string[] = [];
|
||||
let origVals: string[] = [];
|
||||
for (let key in map) {
|
||||
origKeys.push(key);
|
||||
origVals.push(map[key]);
|
||||
}
|
||||
let newMap: {[key: string]: string} = {};
|
||||
for (let i = 0; i < origKeys.length; i++) {
|
||||
let newKey = origVals[i];
|
||||
let newVal = origKeys[i];
|
||||
newMap[newKey] = newVal;
|
||||
}
|
||||
return newMap;
|
||||
}
|
||||
class Allocator {
|
||||
private opts: AllocatorOpts;
|
||||
private availablePowerPins = {
|
||||
top: {
|
||||
threeVolt: mkRange(26, 51).map(n => <BBRowCol>["+", `${n}`]),
|
||||
ground: mkRange(26, 51).map(n => <BBRowCol>["-", `${n}`]),
|
||||
},
|
||||
bottom: {
|
||||
threeVolt: mkRange(1, 26).map(n => <BBRowCol>["+", `${n}`]),
|
||||
ground: mkRange(1, 26).map(n => <BBRowCol>["-", `${n}`]),
|
||||
},
|
||||
};
|
||||
|
||||
constructor(opts: AllocatorOpts) {
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
private allocateLocation(location: WireLocationDefinition, opts: AllocLocOpts): Loc {
|
||||
if (location === "ground" || location === "threeVolt") {
|
||||
U.assert(!!opts.nearestBBPin);
|
||||
let nearLoc = opts.nearestBBPin;
|
||||
let nearestCoord = this.opts.getBBCoord(nearLoc);
|
||||
let firstTopAndBot = [
|
||||
this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
|
||||
this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
|
||||
].map(loc => {
|
||||
return this.opts.getBBCoord(loc);
|
||||
});
|
||||
if (!firstTopAndBot[0] || !firstTopAndBot[1]) {
|
||||
console.debug(`No more available "${location}" locations!`);
|
||||
//TODO
|
||||
}
|
||||
let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
|
||||
let pins: BBRowCol[];
|
||||
if (nearTop) {
|
||||
if (location === "ground") {
|
||||
pins = this.availablePowerPins.top.ground;
|
||||
} else if (location === "threeVolt") {
|
||||
pins = this.availablePowerPins.top.threeVolt;
|
||||
}
|
||||
} else {
|
||||
if (location === "ground") {
|
||||
pins = this.availablePowerPins.bottom.ground;
|
||||
} else if (location === "threeVolt") {
|
||||
pins = this.availablePowerPins.bottom.threeVolt;
|
||||
}
|
||||
}
|
||||
let pinCoords = pins.map(rowCol => {
|
||||
return this.opts.getBBCoord(rowCol);
|
||||
});
|
||||
let pinIdx = visuals.findClosestCoordIdx(nearestCoord, pinCoords);
|
||||
let pin = pins[pinIdx];
|
||||
if (nearTop) {
|
||||
this.availablePowerPins.top.ground.splice(pinIdx, 1);
|
||||
this.availablePowerPins.top.threeVolt.splice(pinIdx, 1);
|
||||
} else {
|
||||
this.availablePowerPins.bottom.ground.splice(pinIdx, 1);
|
||||
this.availablePowerPins.bottom.threeVolt.splice(pinIdx, 1);
|
||||
}
|
||||
return {type: "breadboard", rowCol: pin};
|
||||
} else if (location[0] === "breadboard") {
|
||||
U.assert(!!opts.startColumn);
|
||||
let row = <string>location[1];
|
||||
let col = (<number>location[2] + opts.startColumn).toString();
|
||||
return {type: "breadboard", rowCol: [row, col]}
|
||||
} else if (location[0] === "GPIO") {
|
||||
U.assert(!!opts.cmpGPIOPins);
|
||||
let idx = <number>location[1];
|
||||
let pin = opts.cmpGPIOPins[idx];
|
||||
return {type: "dalboard", pin: pin};
|
||||
} else {
|
||||
//TODO
|
||||
U.assert(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private allocatePowerWires(): WireInst[] {
|
||||
let boardGround = this.opts.boardDef.groundPins[0] || null;
|
||||
if (!boardGround) {
|
||||
console.log("No available ground pin on board!");
|
||||
//TODO
|
||||
}
|
||||
let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
|
||||
if (!threeVoltPin) {
|
||||
console.log("No available 3.3V pin on board!");
|
||||
//TODO
|
||||
}
|
||||
let topLeft: BBRowCol = ["-", "26"];
|
||||
let botLeft: BBRowCol = ["-", "1"];
|
||||
let topRight: BBRowCol = ["-", "50"];
|
||||
let botRight: BBRowCol = ["-", "25"];
|
||||
let top: BBRowCol, bot: BBRowCol;
|
||||
if (this.opts.boardDef.attachPowerOnRight) {
|
||||
top = topRight;
|
||||
bot = botRight;
|
||||
} else {
|
||||
top = topLeft;
|
||||
bot = botLeft;
|
||||
}
|
||||
const GROUND_COLOR = "blue";
|
||||
const POWER_COLOR = "red";
|
||||
const wires: WireInst[] = [
|
||||
{start: this.allocateLocation("ground", {nearestBBPin: top}),
|
||||
end: this.allocateLocation("ground", {nearestBBPin: bot}),
|
||||
color: GROUND_COLOR, assemblyStep: 0},
|
||||
{start: this.allocateLocation("ground", {nearestBBPin: top}),
|
||||
end: {type: "dalboard", pin: boardGround},
|
||||
color: GROUND_COLOR, assemblyStep: 0},
|
||||
{start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
|
||||
end: this.allocateLocation("threeVolt", {nearestBBPin: bot}),
|
||||
color: POWER_COLOR, assemblyStep: 1},
|
||||
{start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
|
||||
end: {type: "dalboard", pin: threeVoltPin},
|
||||
color: POWER_COLOR, assemblyStep: 1},
|
||||
];
|
||||
return wires;
|
||||
}
|
||||
private allocateWire(wireDef: WireDefinition, opts: AllocWireOpts): WireInst {
|
||||
let ends = [wireDef.start, wireDef.end];
|
||||
let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
|
||||
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocateLocation(e, opts) : null)
|
||||
endInsts = endInsts.map((e, idx) => {
|
||||
if (e)
|
||||
return e;
|
||||
let locInst = <BBLoc>endInsts[1 - idx];
|
||||
let l = this.allocateLocation(ends[idx], {
|
||||
nearestBBPin: locInst.rowCol,
|
||||
startColumn: opts.startColumn,
|
||||
cmpGPIOPins: opts.cmpGPIOPins
|
||||
});
|
||||
return l;
|
||||
});
|
||||
return {start: endInsts[0], end: endInsts[1], color: wireDef.color, assemblyStep: wireDef.assemblyStep};
|
||||
}
|
||||
private allocatePartialCmps(): PartialCmpAlloc[] {
|
||||
let cmpNmAndDefs = this.opts.cmpList.map(cmpName => <[string, PartDefinition]>[cmpName, this.opts.cmpDefs[cmpName]]).filter(d => !!d[1]);
|
||||
let cmpNmsList = cmpNmAndDefs.map(p => p[0]);
|
||||
let cmpDefsList = cmpNmAndDefs.map(p => p[1]);
|
||||
let partialCmps: PartialCmpAlloc[] = [];
|
||||
cmpDefsList.forEach((def, idx) => {
|
||||
let nm = cmpNmsList[idx];
|
||||
if (def.pinAllocation.type === "predefined") {
|
||||
let mbPins = (<PredefinedPinAlloc>def.pinAllocation).pins;
|
||||
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]);
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: pinsAssigned,
|
||||
pinsNeeded: 0,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
});
|
||||
} else if (def.pinAllocation.type === "factoryfunction") {
|
||||
let fnPinAlloc = (<FactoryFunctionPinAlloc>def.pinAllocation);
|
||||
let fnNm = fnPinAlloc.functionName;
|
||||
let fnsAndArgs = <string[]>this.opts.fnArgs[fnNm];
|
||||
let success = false;
|
||||
if (fnsAndArgs && fnsAndArgs.length) {
|
||||
let pinArgPoses = fnPinAlloc.pinArgPositions;
|
||||
let otherArgPoses = fnPinAlloc.otherArgPositions || [];
|
||||
fnsAndArgs.forEach(fnArgsStr => {
|
||||
let fnArgsSplit = fnArgsStr.split(",");
|
||||
let pinArgs: string[] = [];
|
||||
pinArgPoses.forEach(i => {
|
||||
pinArgs.push(fnArgsSplit[i]);
|
||||
});
|
||||
let mbPins = pinArgs.map(arg => readPin(arg));
|
||||
let otherArgs: string[] = [];
|
||||
otherArgPoses.forEach(i => {
|
||||
otherArgs.push(fnArgsSplit[i]);
|
||||
});
|
||||
let pinsAssigned = mbPins.map(p => this.opts.boardDef.gpioPinMap[p]);
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: pinsAssigned,
|
||||
pinsNeeded: 0,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
otherArgs: otherArgs.length ? otherArgs : null,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// failed to find pin allocation from callsites
|
||||
console.debug("Failed to read pin(s) from callsite for: " + fnNm);
|
||||
let pinsNeeded = fnPinAlloc.pinArgPositions.length;
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: [],
|
||||
pinsNeeded: pinsNeeded,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
});
|
||||
}
|
||||
} else if (def.pinAllocation.type === "auto") {
|
||||
let pinsNeeded = (<AutoPinAlloc>def.pinAllocation).gpioPinsNeeded;
|
||||
partialCmps.push({
|
||||
name: nm,
|
||||
def: def,
|
||||
pinsAssigned: [],
|
||||
pinsNeeded: pinsNeeded,
|
||||
breadboardColumnsNeeded: def.breadboardColumnsNeeded,
|
||||
});
|
||||
}
|
||||
});
|
||||
return partialCmps;
|
||||
}
|
||||
private allocateGPIOPins(partialCmps: PartialCmpAlloc[]): string[][] {
|
||||
let availableGPIOBlocks = copyDoubleArray(this.opts.boardDef.gpioPinBlocks);
|
||||
let sortAvailableGPIOBlocks = () => availableGPIOBlocks.sort((a, b) => a.length - b.length); //smallest blocks first
|
||||
// determine blocks needed
|
||||
let blockAssignments: AllocBlock[] = [];
|
||||
let preassignedPins: string[] = [];
|
||||
partialCmps.forEach((cmp, idx) => {
|
||||
if (cmp.pinsAssigned && cmp.pinsAssigned.length) {
|
||||
//already assigned
|
||||
blockAssignments.push({cmpIdx: idx, cmpBlkIdx: 0, gpioNeeded: 0, gpioAssigned: cmp.pinsAssigned});
|
||||
preassignedPins = preassignedPins.concat(cmp.pinsAssigned);
|
||||
} else if (cmp.pinsNeeded) {
|
||||
if (typeof cmp.pinsNeeded === "number") {
|
||||
//individual pins
|
||||
for (let i = 0; i < cmp.pinsNeeded; i++) {
|
||||
blockAssignments.push(
|
||||
{cmpIdx: idx, cmpBlkIdx: 0, gpioNeeded: 1, gpioAssigned: []});
|
||||
}
|
||||
} else {
|
||||
//blocks of pins
|
||||
let blocks = <number[]>cmp.pinsNeeded;
|
||||
blocks.forEach((numNeeded, blkIdx) => {
|
||||
blockAssignments.push(
|
||||
{cmpIdx: idx, cmpBlkIdx: blkIdx, gpioNeeded: numNeeded, gpioAssigned: []});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// remove assigned blocks
|
||||
availableGPIOBlocks.forEach(blks => {
|
||||
for (let i = blks.length - 1; 0 <= i; i--) {
|
||||
let pin = blks[i];
|
||||
if (0 <= preassignedPins.indexOf(pin)) {
|
||||
blks.splice(i, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
// sort by size of blocks
|
||||
let sortBlockAssignments = () => blockAssignments.sort((a, b) => b.gpioNeeded - a.gpioNeeded); //largest blocks first
|
||||
// allocate each block
|
||||
if (0 < blockAssignments.length && 0 < availableGPIOBlocks.length) {
|
||||
do {
|
||||
sortBlockAssignments();
|
||||
sortAvailableGPIOBlocks();
|
||||
let assignment = blockAssignments[0];
|
||||
let smallestAvailableBlockThatFits: string[];
|
||||
for (let j = 0; j < availableGPIOBlocks.length; j++) {
|
||||
smallestAvailableBlockThatFits = availableGPIOBlocks[j];
|
||||
if (assignment.gpioNeeded <= availableGPIOBlocks[j].length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!smallestAvailableBlockThatFits || smallestAvailableBlockThatFits.length <= 0) {
|
||||
break; // out of pins
|
||||
}
|
||||
while (0 < assignment.gpioNeeded && 0 < smallestAvailableBlockThatFits.length) {
|
||||
assignment.gpioNeeded--;
|
||||
let pin = smallestAvailableBlockThatFits[0];
|
||||
smallestAvailableBlockThatFits.splice(0, 1);
|
||||
assignment.gpioAssigned.push(pin);
|
||||
}
|
||||
sortBlockAssignments();
|
||||
} while (0 < blockAssignments[0].gpioNeeded);
|
||||
}
|
||||
if (0 < blockAssignments.length && 0 < blockAssignments[0].gpioNeeded) {
|
||||
console.debug("Not enough GPIO pins!");
|
||||
return null;
|
||||
}
|
||||
let cmpGPIOPinBlocks: string[][][] = partialCmps.map((def, cmpIdx) => {
|
||||
if (!def)
|
||||
return null;
|
||||
let assignments = blockAssignments.filter(a => a.cmpIdx === cmpIdx);
|
||||
let gpioPins: string[][] = [];
|
||||
for (let i = 0; i < assignments.length; i++) {
|
||||
let a = assignments[i];
|
||||
let blk = gpioPins[a.cmpBlkIdx] || (gpioPins[a.cmpBlkIdx] = []);
|
||||
a.gpioAssigned.forEach(p => blk.push(p));
|
||||
}
|
||||
return gpioPins;
|
||||
});
|
||||
let cmpGPIOPins = cmpGPIOPinBlocks.map(blks => blks.reduce((p, n) => p.concat(n), []));
|
||||
return cmpGPIOPins;
|
||||
}
|
||||
private allocateColumns(partialCmps: PartialCmpAlloc[]): number[] {
|
||||
let componentsCount = partialCmps.length;
|
||||
let totalAvailableSpace = 30; //TODO allow multiple breadboards
|
||||
let totalSpaceNeeded = partialCmps.map(d => d.breadboardColumnsNeeded).reduce((p, n) => p + n, 0);
|
||||
let extraSpace = totalAvailableSpace - totalSpaceNeeded;
|
||||
if (extraSpace <= 0) {
|
||||
console.log("Not enough breadboard space!");
|
||||
//TODO
|
||||
}
|
||||
let padding = Math.floor(extraSpace / (componentsCount - 1 + 2));
|
||||
let componentSpacing = padding; //Math.floor(extraSpace/(componentsCount-1));
|
||||
let totalCmpPadding = extraSpace - componentSpacing * (componentsCount - 1);
|
||||
let leftPadding = Math.floor(totalCmpPadding / 2);
|
||||
let rightPadding = Math.ceil(totalCmpPadding / 2);
|
||||
let nextAvailableCol = 1 + leftPadding;
|
||||
let cmpStartCol = partialCmps.map(cmp => {
|
||||
let col = nextAvailableCol;
|
||||
nextAvailableCol += cmp.breadboardColumnsNeeded + componentSpacing;
|
||||
return col;
|
||||
});
|
||||
return cmpStartCol;
|
||||
}
|
||||
private allocateComponent(partialCmp: PartialCmpAlloc, startColumn: number, microbitPins: string[]): CmpInst {
|
||||
return {
|
||||
name: partialCmp.name,
|
||||
breadboardStartColumn: startColumn,
|
||||
breadboardStartRow: partialCmp.def.breadboardStartRow,
|
||||
assemblyStep: partialCmp.def.assemblyStep,
|
||||
visual: partialCmp.def.visual,
|
||||
microbitPins: microbitPins,
|
||||
otherArgs: partialCmp.otherArgs,
|
||||
};
|
||||
}
|
||||
public allocateAll(): AllocatorResult {
|
||||
let cmpList = this.opts.cmpList;
|
||||
let basicWires: WireInst[] = [];
|
||||
let cmpsAndWires: CmpAndWireInst[] = [];
|
||||
if (cmpList.length > 0) {
|
||||
basicWires = this.allocatePowerWires();
|
||||
let partialCmps = this.allocatePartialCmps();
|
||||
let cmpGPIOPins = this.allocateGPIOPins(partialCmps);
|
||||
let reverseMap = mkReverseMap(this.opts.boardDef.gpioPinMap);
|
||||
let cmpMicrobitPins = cmpGPIOPins.map(pins => pins.map(p => reverseMap[p]));
|
||||
let cmpStartCol = this.allocateColumns(partialCmps);
|
||||
let cmps = partialCmps.map((c, idx) => this.allocateComponent(c, cmpStartCol[idx], cmpMicrobitPins[idx]));
|
||||
let wires = partialCmps.map((c, idx) => c.def.wires.map(d => this.allocateWire(d, {
|
||||
cmpGPIOPins: cmpGPIOPins[idx],
|
||||
startColumn: cmpStartCol[idx],
|
||||
})));
|
||||
cmpsAndWires = cmps.map((c, idx) => {
|
||||
return {component: c, wires: wires[idx]}
|
||||
});
|
||||
}
|
||||
return {
|
||||
powerWires: basicWires,
|
||||
components: cmpsAndWires
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function allocateDefinitions(opts: AllocatorOpts): AllocatorResult {
|
||||
return new Allocator(opts).allocateAll();
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export class DalBoard extends BaseBoard {
|
||||
id: string;
|
||||
|
||||
// the bus
|
||||
bus: EventBus;
|
||||
bus: pxsim.EventBus;
|
||||
|
||||
// state & update logic for component services
|
||||
ledMatrixState: LedMatrixState;
|
||||
@ -23,11 +25,17 @@ namespace pxsim {
|
||||
constructor() {
|
||||
super()
|
||||
this.id = "b" + Math_.random(2147483647);
|
||||
this.bus = new EventBus(runtime);
|
||||
this.bus = new pxsim.EventBus(runtime);
|
||||
|
||||
// components
|
||||
this.ledMatrixState = new LedMatrixState(runtime);
|
||||
this.buttonPairState = new ButtonPairState();
|
||||
this.buttonPairState = new ButtonPairState({
|
||||
ID_BUTTON_A: DAL.MICROBIT_ID_BUTTON_A,
|
||||
ID_BUTTON_B: DAL.MICROBIT_ID_BUTTON_B,
|
||||
ID_BUTTON_AB: DAL.MICROBIT_ID_BUTTON_AB,
|
||||
BUTTON_EVT_UP: DAL.MICROBIT_BUTTON_EVT_UP,
|
||||
BUTTON_EVT_CLICK: DAL.MICROBIT_BUTTON_EVT_CLICK
|
||||
});
|
||||
this.edgeConnectorState = new EdgeConnectorState();
|
||||
this.radioState = new RadioState(runtime);
|
||||
this.accelerometerState = new AccelerometerState(runtime);
|
||||
@ -69,19 +77,21 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||
super.initAsync(msg);
|
||||
|
||||
let options = (msg.options || {}) as RuntimeOptions;
|
||||
|
||||
let boardDef = CURRENT_BOARD; //TODO: read from pxt.json/pxttarget.json
|
||||
|
||||
let cmpsList = msg.parts;
|
||||
let cmpDefs = PART_DEFINITIONS; //TODO: read from pxt.json/pxttarget.json
|
||||
let cmpDefs = msg.partDefinitions || {}; //TODO: read from pxt.json/pxttarget.json
|
||||
let fnArgs = msg.fnArgs;
|
||||
|
||||
let viewHost = new visuals.BoardHost({
|
||||
state: this,
|
||||
boardDef: boardDef,
|
||||
cmpsList: cmpsList,
|
||||
cmpDefs: cmpDefs,
|
||||
partsList: cmpsList,
|
||||
partDefs: cmpDefs,
|
||||
fnArgs: fnArgs,
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
@ -93,4 +103,37 @@ namespace pxsim {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export function initRuntimeWithDalBoard() {
|
||||
U.assert(!runtime.board);
|
||||
let b = new DalBoard();
|
||||
runtime.board = b;
|
||||
runtime.postError = (e) => {
|
||||
led.setBrightness(255);
|
||||
let img = board().ledMatrixState.image;
|
||||
img.clear();
|
||||
img.set(0, 4, 255);
|
||||
img.set(1, 3, 255);
|
||||
img.set(2, 3, 255);
|
||||
img.set(3, 3, 255);
|
||||
img.set(4, 4, 255);
|
||||
img.set(0, 0, 255);
|
||||
img.set(1, 0, 255);
|
||||
img.set(0, 1, 255);
|
||||
img.set(1, 1, 255);
|
||||
img.set(3, 0, 255);
|
||||
img.set(4, 0, 255);
|
||||
img.set(3, 1, 255);
|
||||
img.set(4, 1, 255);
|
||||
runtime.updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
if (!pxsim.initCurrentRuntime) {
|
||||
pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
|
||||
}
|
||||
|
||||
export function board() {
|
||||
return runtime.board as DalBoard;
|
||||
}
|
||||
}
|
@ -1,73 +1,10 @@
|
||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtparts.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../libs/microbit/dal.d.ts"/>
|
||||
/// <reference path="./visuals/neopixel.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export interface PinBlockDefinition {
|
||||
x: number,
|
||||
y: number,
|
||||
labelPosition: "above" | "below";
|
||||
labels: string[]
|
||||
}
|
||||
export interface BoardImageDefinition {
|
||||
image: string,
|
||||
outlineImage?: string,
|
||||
width: number,
|
||||
height: number,
|
||||
pinDist: number,
|
||||
pinBlocks: PinBlockDefinition[],
|
||||
};
|
||||
export interface BoardDefinition {
|
||||
visual: BoardImageDefinition | string,
|
||||
gpioPinBlocks?: string[][],
|
||||
gpioPinMap: {[pin: string]: string},
|
||||
groundPins: string[],
|
||||
threeVoltPins: string[],
|
||||
attachPowerOnRight?: boolean,
|
||||
onboardComponents?: string[]
|
||||
useCrocClips?: boolean,
|
||||
marginWhenBreadboarding?: [number, number, number, number],
|
||||
}
|
||||
export interface FactoryFunctionPinAlloc {
|
||||
type: "factoryfunction",
|
||||
functionName: string,
|
||||
pinArgPositions: number[],
|
||||
otherArgPositions?: number[],
|
||||
}
|
||||
export interface PredefinedPinAlloc {
|
||||
type: "predefined",
|
||||
pins: string[],
|
||||
}
|
||||
export interface AutoPinAlloc {
|
||||
type: "auto",
|
||||
gpioPinsNeeded: number | number[],
|
||||
}
|
||||
export interface PartVisualDefinition {
|
||||
image: string,
|
||||
width: number,
|
||||
height: number,
|
||||
pinDist: number,
|
||||
extraColumnOffset?: number,
|
||||
firstPin: [number, number],
|
||||
}
|
||||
export interface PartDefinition {
|
||||
visual: string | PartVisualDefinition,
|
||||
breadboardColumnsNeeded: number,
|
||||
breadboardStartRow: string,
|
||||
wires: WireDefinition[],
|
||||
assemblyStep: number,
|
||||
pinAllocation: FactoryFunctionPinAlloc | PredefinedPinAlloc | AutoPinAlloc,
|
||||
}
|
||||
export interface WireDefinition {
|
||||
start: WireLocationDefinition,
|
||||
end: WireLocationDefinition,
|
||||
color: string,
|
||||
assemblyStep: number
|
||||
};
|
||||
export type WireLocationDefinition =
|
||||
["breadboard", string, number] | ["GPIO", number] | "ground" | "threeVolt";
|
||||
|
||||
export const MICROBIT_DEF: BoardDefinition = {
|
||||
visual: "microbit",
|
||||
gpioPinBlocks: [
|
||||
@ -75,8 +12,7 @@ namespace pxsim {
|
||||
["P3"],
|
||||
["P4", "P5", "P6", "P7"],
|
||||
["P8", "P9", "P10", "P11", "P12"],
|
||||
["P13", "P14", "P15", "P16"],
|
||||
["P19", "P20"],
|
||||
["P16"],
|
||||
],
|
||||
gpioPinMap: {
|
||||
"P0": "P0",
|
||||
@ -99,6 +35,16 @@ namespace pxsim {
|
||||
"P19": "P19",
|
||||
"P20": "P20",
|
||||
},
|
||||
spiPins: {
|
||||
MOSI: "P15",
|
||||
MISO: "P14",
|
||||
SCK: "P13",
|
||||
},
|
||||
i2cPins: {
|
||||
SDA: "P20",
|
||||
SCL: "P19",
|
||||
},
|
||||
analogInPins: ["P0", "P1", "P2", "P3", "P10"],
|
||||
groundPins: ["GND"],
|
||||
threeVoltPins: ["+3v3"],
|
||||
attachPowerOnRight: true,
|
||||
@ -107,86 +53,7 @@ namespace pxsim {
|
||||
marginWhenBreadboarding: [0, 0, 80, 0],
|
||||
}
|
||||
|
||||
export const PART_DEFINITIONS: Map<PartDefinition> = {
|
||||
"ledmatrix": {
|
||||
visual: "ledmatrix",
|
||||
breadboardColumnsNeeded: 8,
|
||||
breadboardStartRow: "h",
|
||||
pinAllocation: {
|
||||
type: "auto",
|
||||
gpioPinsNeeded: [5, 5],
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", `j`, 0], end: ["GPIO", 5], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `j`, 1], end: ["GPIO", 6], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `j`, 2], end: ["GPIO", 7], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `j`, 3], end: ["GPIO", 8], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `a`, 7], end: ["GPIO", 9], color: "purple", assemblyStep: 1},
|
||||
{start: ["breadboard", `a`, 0], end: ["GPIO", 0], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `a`, 1], end: ["GPIO", 1], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `a`, 2], end: ["GPIO", 2], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `a`, 3], end: ["GPIO", 3], color: "green", assemblyStep: 2},
|
||||
{start: ["breadboard", `j`, 4], end: ["GPIO", 4], color: "green", assemblyStep: 2},
|
||||
]
|
||||
},
|
||||
"buttonpair": {
|
||||
visual: "buttonpair",
|
||||
breadboardColumnsNeeded: 6,
|
||||
breadboardStartRow: "f",
|
||||
pinAllocation: {
|
||||
type: "predefined",
|
||||
pins: ["P13", "P12"],
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 0], end: ["GPIO", 0], color: "yellow", assemblyStep: 1},
|
||||
{start: ["breadboard", "a", 2], end: "ground", color: "blue", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 3], end: ["GPIO", 1], color: "orange", assemblyStep: 2},
|
||||
{start: ["breadboard", "a", 5], end: "ground", color: "blue", assemblyStep: 2},
|
||||
],
|
||||
},
|
||||
"neopixel": {
|
||||
visual: "neopixel",
|
||||
breadboardColumnsNeeded: 5,
|
||||
breadboardStartRow: "h",
|
||||
pinAllocation: {
|
||||
type: "factoryfunction",
|
||||
functionName: "neopixel.create",
|
||||
pinArgPositions: [0],
|
||||
otherArgPositions: [1],
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 1], end: "ground", color: "blue", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 2], end: "threeVolt", color: "red", assemblyStep: 2},
|
||||
{start: ["breadboard", "j", 3], end: ["GPIO", 0], color: "green", assemblyStep: 2},
|
||||
],
|
||||
},
|
||||
"speaker": {
|
||||
visual: {
|
||||
image: "/parts/speaker.svg",
|
||||
width: 500,
|
||||
height: 500,
|
||||
firstPin: [180, 135],
|
||||
pinDist: 70,
|
||||
extraColumnOffset: 1,
|
||||
},
|
||||
breadboardColumnsNeeded: 5,
|
||||
breadboardStartRow: "f",
|
||||
pinAllocation: {
|
||||
type: "auto",
|
||||
gpioPinsNeeded: 1,
|
||||
},
|
||||
assemblyStep: 0,
|
||||
wires: [
|
||||
{start: ["breadboard", "j", 1], end: ["GPIO", 0], color: "#ff80fa", assemblyStep: 1},
|
||||
{start: ["breadboard", "j", 3], end: "ground", color: "blue", assemblyStep: 1},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const builtinComponentSimVisual: Map<() => visuals.IBoardComponent<any>> = {
|
||||
export const builtinComponentSimVisual: Map<() => visuals.IBoardPart<any>> = {
|
||||
"buttonpair": () => new visuals.ButtonPairView(),
|
||||
"ledmatrix": () => new visuals.LedMatrixView(),
|
||||
"neopixel": () => new visuals.NeoPixelView(),
|
||||
|
@ -1,9 +1,6 @@
|
||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtrunner.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
/// <reference path="../visuals/genericboard.ts"/>
|
||||
/// <reference path="../visuals/wiring.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtrunner.d.ts"/>
|
||||
|
||||
//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
|
||||
if (!(<any>window).pxt) (<any>window).pxt = {};
|
||||
@ -20,34 +17,37 @@ namespace pxsim.instructions {
|
||||
const LBL_LEFT_PAD = 5;
|
||||
const REQ_WIRE_HEIGHT = 45;
|
||||
const REQ_CMP_HEIGHT = 55;
|
||||
const REQ_CMP_SCALE = 0.5;
|
||||
const REQ_CMP_SCALE = 0.5 * 3;
|
||||
type Orientation = "landscape" | "portrait";
|
||||
const ORIENTATION: Orientation = "portrait";
|
||||
const PPI = 96.0;
|
||||
const PAGE_SCALAR = 0.95;
|
||||
const [FULL_PAGE_WIDTH, FULL_PAGE_HEIGHT]
|
||||
= (ORIENTATION == "portrait" ? [PPI * 8.5, PPI * 11.0] : [PPI * 11.0, PPI * 8.5]);
|
||||
= (ORIENTATION == "portrait" ? [PPI * 8.5 * PAGE_SCALAR, PPI * 11.0 * PAGE_SCALAR] : [PPI * 11.0 * PAGE_SCALAR, PPI * 8.5 * PAGE_SCALAR]);
|
||||
const PAGE_MARGIN = PPI * 0.45;
|
||||
const PAGE_WIDTH = FULL_PAGE_WIDTH - PAGE_MARGIN * 2;
|
||||
const PAGE_HEIGHT = FULL_PAGE_HEIGHT - PAGE_MARGIN * 2;
|
||||
const BORDER_COLOR = "gray";
|
||||
const BORDER_RADIUS = 5;
|
||||
const BORDER_WIDTH = 2;
|
||||
const [PANEL_ROWS, PANEL_COLS] = [2, 2];
|
||||
const BORDER_RADIUS = 5 * 4;
|
||||
const BORDER_WIDTH = 2 * 2;
|
||||
const [PANEL_ROWS, PANEL_COLS] = [1, 1];
|
||||
const PANEL_MARGIN = 20;
|
||||
const PANEL_PADDING = 8;
|
||||
const PANEL_PADDING = 8 * 3;
|
||||
const PANEL_WIDTH = PAGE_WIDTH / PANEL_COLS - (PANEL_MARGIN + PANEL_PADDING + BORDER_WIDTH) * PANEL_COLS;
|
||||
const PANEL_HEIGHT = PAGE_HEIGHT / PANEL_ROWS - (PANEL_MARGIN + PANEL_PADDING + BORDER_WIDTH) * PANEL_ROWS;
|
||||
const BOARD_WIDTH = 240;
|
||||
const BOARD_WIDTH = 465;
|
||||
const BOARD_LEFT = (PANEL_WIDTH - BOARD_WIDTH) / 2.0 + PANEL_PADDING;
|
||||
const BOARD_BOT = PANEL_PADDING;
|
||||
const NUM_BOX_SIZE = 60;
|
||||
const NUM_FONT = 40;
|
||||
const NUM_MARGIN = 5;
|
||||
const FRONT_PAGE_BOARD_WIDTH = 200;
|
||||
const NUM_BOX_SIZE = 120;
|
||||
const NUM_FONT = 80;
|
||||
const NUM_MARGIN = 10;
|
||||
const FRONT_PAGE_BOARD_WIDTH = 400;
|
||||
const PART_SCALAR = 2.3
|
||||
const PARTS_BOARD_SCALE = 0.17;
|
||||
const PARTS_BB_SCALE = 0.25;
|
||||
const PARTS_CMP_SCALE = 0.3;
|
||||
const PARTS_WIRE_SCALE = 0.23;
|
||||
const BACK_PAGE_BOARD_WIDTH = PANEL_WIDTH - PANEL_PADDING * 1.5;
|
||||
const STYLE = `
|
||||
.instr-panel {
|
||||
margin: ${PANEL_MARGIN}px;
|
||||
@ -61,6 +61,7 @@ namespace pxsim.instructions {
|
||||
height: ${PANEL_HEIGHT}px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.board-svg {
|
||||
margin: 0 auto;
|
||||
@ -90,10 +91,11 @@ namespace pxsim.instructions {
|
||||
}
|
||||
.reqs-div {
|
||||
margin-left: ${PANEL_PADDING + NUM_BOX_SIZE}px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.partslist-wire,
|
||||
.partslist-cmp {
|
||||
margin: 5px;
|
||||
margin: 10px;
|
||||
}
|
||||
.partslist-wire {
|
||||
display: inline-block;
|
||||
@ -126,7 +128,8 @@ namespace pxsim.instructions {
|
||||
wireClr?: string,
|
||||
cmpWidth?: number,
|
||||
cmpHeight?: number,
|
||||
cmpScale?: number
|
||||
cmpScale?: number,
|
||||
crocClips?: boolean
|
||||
};
|
||||
function mkBoardImgSvg(def: string | BoardImageDefinition): visuals.SVGElAndSize {
|
||||
let boardView: visuals.BoardView;
|
||||
@ -149,7 +152,7 @@ namespace pxsim.instructions {
|
||||
//TODO: Refactor this function; it is too complicated. There is a lot of error-prone math being done
|
||||
// to scale and place all elements which could be simplified with more forethought.
|
||||
let svgEl = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
let dims = {l: 0, t: 0, w: 0, h: 0};
|
||||
let dims = { l: 0, t: 0, w: 0, h: 0 };
|
||||
|
||||
let cmpSvgEl = <SVGSVGElement>document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svgEl.appendChild(cmpSvgEl);
|
||||
@ -176,7 +179,7 @@ namespace pxsim.instructions {
|
||||
scale(opts.cmpHeight / dims.h)
|
||||
}
|
||||
svg.hydrate(cmpSvgEl, cmpSvgAtts);
|
||||
let elDims = {l: dims.l, t: dims.t, w: dims.w, h: dims.h};
|
||||
let elDims = { l: dims.l, t: dims.t, w: dims.w, h: dims.h };
|
||||
|
||||
let updateL = (newL: number) => {
|
||||
if (newL < dims.l) {
|
||||
@ -269,8 +272,8 @@ namespace pxsim.instructions {
|
||||
|
||||
let svgAtts = {
|
||||
"viewBox": `${dims.l} ${dims.t} ${dims.w} ${dims.h}`,
|
||||
"width": dims.w,
|
||||
"height": dims.h,
|
||||
"width": dims.w * PART_SCALAR,
|
||||
"height": dims.h * PART_SCALAR,
|
||||
"preserveAspectRatio": "xMidYMid",
|
||||
};
|
||||
svg.hydrate(svgEl, svgAtts);
|
||||
@ -278,18 +281,18 @@ namespace pxsim.instructions {
|
||||
div.appendChild(svgEl);
|
||||
return div;
|
||||
}
|
||||
function mkCmpDiv(cmp: "wire" | string | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
|
||||
function mkCmpDiv(cmp: "wire" | PartVisualDefinition, opts: mkCmpDivOpts): HTMLElement {
|
||||
let el: visuals.SVGElAndSize;
|
||||
if (cmp == "wire") {
|
||||
//TODO: support non-croc wire parts
|
||||
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", true);
|
||||
} else if (typeof cmp == "string") {
|
||||
let builtinVis = <string>cmp;
|
||||
let cnstr = builtinComponentPartVisual[builtinVis];
|
||||
el = cnstr([0, 0]);
|
||||
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", opts.crocClips);
|
||||
} else {
|
||||
let partVis = <PartVisualDefinition> cmp;
|
||||
el = visuals.mkGenericPartSVG(partVis);
|
||||
let partVis = <PartVisualDefinition>cmp;
|
||||
if (typeof partVis.builtIn == "string") {
|
||||
let cnstr = builtinComponentPartVisual[partVis.builtIn];
|
||||
el = cnstr([0, 0]);
|
||||
} else {
|
||||
el = visuals.mkGenericPartSVG(partVis);
|
||||
}
|
||||
}
|
||||
return wrapSvg(el, opts);
|
||||
}
|
||||
@ -299,40 +302,33 @@ namespace pxsim.instructions {
|
||||
fnArgs: any,
|
||||
allAlloc: AllocatorResult,
|
||||
stepToWires: WireInst[][],
|
||||
stepToCmps: CmpInst[][]
|
||||
stepToCmps: PartInst[][]
|
||||
allWires: WireInst[],
|
||||
allCmps: CmpInst[],
|
||||
allCmps: PartInst[],
|
||||
lastStep: number,
|
||||
colorToWires: Map<WireInst[]>,
|
||||
allWireColors: string[],
|
||||
};
|
||||
function mkBoardProps(allocOpts: AllocatorOpts): BoardProps {
|
||||
let allocRes = allocateDefinitions(allocOpts);
|
||||
let {powerWires, components} = allocRes;
|
||||
let stepToWires: WireInst[][] = [];
|
||||
let stepToCmps: CmpInst[][] = [];
|
||||
powerWires.forEach(w => {
|
||||
let step = w.assemblyStep + 1;
|
||||
(stepToWires[step] || (stepToWires[step] = [])).push(w)
|
||||
});
|
||||
let getMaxStep = (ns: {assemblyStep: number}[]) => ns.reduce((m, n) => Math.max(m, n.assemblyStep), 0);
|
||||
let stepOffset = getMaxStep(powerWires) + 2;
|
||||
components.forEach(cAndWs => {
|
||||
let {component, wires} = cAndWs;
|
||||
let cStep = component.assemblyStep + stepOffset;
|
||||
let arr = stepToCmps[cStep] || (stepToCmps[cStep] = []);
|
||||
arr.push(component);
|
||||
let wSteps = wires.map(w => w.assemblyStep + stepOffset);
|
||||
wires.forEach((w, i) => {
|
||||
let wStep = wSteps[i];
|
||||
let arr = stepToWires[wStep] || (stepToWires[wStep] = []);
|
||||
arr.push(w);
|
||||
let stepToCmps: PartInst[][] = [];
|
||||
let stepOffset = 0;
|
||||
allocRes.partsAndWires.forEach(cAndWs => {
|
||||
let part = cAndWs.part;
|
||||
let wires = cAndWs.wires;
|
||||
cAndWs.assembly.forEach((step, idx) => {
|
||||
if (step.part && part)
|
||||
stepToCmps[stepOffset + idx] = [part]
|
||||
if (step.wireIndices && step.wireIndices.length > 0 && wires)
|
||||
stepToWires[stepOffset + idx] = step.wireIndices.map(i => wires[i])
|
||||
})
|
||||
stepOffset = Math.max(cStep, wSteps.reduce((m, n) => Math.max(m, n), 0)) + 1;
|
||||
stepOffset += cAndWs.assembly.length;
|
||||
});
|
||||
let lastStep = stepOffset - 1;
|
||||
let allCmps = components.map(p => p.component);
|
||||
let allWires = powerWires.concat(components.map(p => p.wires).reduce((p, n) => p.concat(n), []));
|
||||
let numSteps = stepOffset;
|
||||
let lastStep = numSteps - 1;
|
||||
let allCmps = allocRes.partsAndWires.map(r => r.part).filter(p => !!p);
|
||||
let allWires = allocRes.partsAndWires.map(r => r.wires || []).reduce((p, n) => p.concat(n), []);
|
||||
let colorToWires: Map<WireInst[]> = {}
|
||||
let allWireColors: string[] = [];
|
||||
allWires.forEach(w => {
|
||||
@ -344,7 +340,7 @@ namespace pxsim.instructions {
|
||||
});
|
||||
return {
|
||||
boardDef: allocOpts.boardDef,
|
||||
cmpDefs: allocOpts.cmpDefs,
|
||||
cmpDefs: allocOpts.partDefs,
|
||||
fnArgs: allocOpts.fnArgs,
|
||||
allAlloc: allocRes,
|
||||
stepToWires: stepToWires,
|
||||
@ -359,10 +355,10 @@ namespace pxsim.instructions {
|
||||
function mkBlankBoardAndBreadboard(boardDef: BoardDefinition, cmpDefs: Map<PartDefinition>, fnArgs: any, width: number, buildMode: boolean = false): visuals.BoardHost {
|
||||
let state = runtime.board as pxsim.DalBoard;
|
||||
let boardHost = new visuals.BoardHost({
|
||||
state: state,
|
||||
boardDef: boardDef,
|
||||
state: state,
|
||||
boardDef: boardDef,
|
||||
forceBreadboard: true,
|
||||
cmpDefs: cmpDefs,
|
||||
partDefs: cmpDefs,
|
||||
maxWidth: `${width}px`,
|
||||
fnArgs: fnArgs,
|
||||
wireframe: buildMode,
|
||||
@ -391,6 +387,19 @@ namespace pxsim.instructions {
|
||||
}
|
||||
|
||||
for (let i = 0; i <= step; i++) {
|
||||
let cmps = props.stepToCmps[i];
|
||||
if (cmps) {
|
||||
cmps.forEach(partInst => {
|
||||
let cmp = board.addPart(partInst)
|
||||
//last step
|
||||
if (i === step) {
|
||||
//highlight locations pins
|
||||
partInst.breadboardConnections.forEach(bbLoc => board.highlightBreadboardPin(bbLoc));
|
||||
svg.addClass(cmp.element, "notgrayed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let wires = props.stepToWires[i];
|
||||
if (wires) {
|
||||
wires.forEach(w => {
|
||||
@ -399,13 +408,12 @@ namespace pxsim.instructions {
|
||||
if (i === step) {
|
||||
//location highlights
|
||||
if (w.start.type == "breadboard") {
|
||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.start).rowCol);
|
||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.start));
|
||||
} else {
|
||||
board.highlightBoardPin((<BoardLoc>w.start).pin);
|
||||
}
|
||||
if (w.end.type == "breadboard") {
|
||||
let [row, col] = (<BBLoc>w.end).rowCol;
|
||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.end).rowCol);
|
||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.end));
|
||||
} else {
|
||||
board.highlightBoardPin((<BoardLoc>w.end).pin);
|
||||
}
|
||||
@ -414,24 +422,6 @@ namespace pxsim.instructions {
|
||||
}
|
||||
});
|
||||
}
|
||||
let cmps = props.stepToCmps[i];
|
||||
if (cmps) {
|
||||
cmps.forEach(cmpInst => {
|
||||
let cmp = board.addComponent(cmpInst)
|
||||
let colOffset = (<any>cmpInst.visual).breadboardStartColIdx || 0;
|
||||
let rowCol: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${colOffset + cmpInst.breadboardStartColumn}`];
|
||||
//last step
|
||||
if (i === step) {
|
||||
board.highlightBreadboardPin(rowCol);
|
||||
if (cmpInst.visual === "buttonpair") {
|
||||
//TODO: don't specialize this
|
||||
let rowCol2: BBRowCol = [`${cmpInst.breadboardStartRow}`, `${cmpInst.breadboardStartColumn + 3}`];
|
||||
board.highlightBreadboardPin(rowCol2);
|
||||
}
|
||||
svg.addClass(cmp.element, "notgrayed");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function mkPanel() {
|
||||
@ -446,10 +436,10 @@ namespace pxsim.instructions {
|
||||
|
||||
// board and breadboard
|
||||
let boardImg = mkBoardImgSvg(props.boardDef.visual);
|
||||
let board = wrapSvg(boardImg, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE});
|
||||
let board = wrapSvg(boardImg, { left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BOARD_SCALE });
|
||||
panel.appendChild(board);
|
||||
let bbRaw = mkBBSvg();
|
||||
let bb = wrapSvg(bbRaw, {left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BB_SCALE});
|
||||
let bb = wrapSvg(bbRaw, { left: QUANT_LBL(1), leftSize: QUANT_LBL_SIZE, cmpScale: PARTS_BB_SCALE });
|
||||
panel.appendChild(bb);
|
||||
|
||||
// components
|
||||
@ -457,7 +447,7 @@ namespace pxsim.instructions {
|
||||
cmps.forEach(c => {
|
||||
let quant = 1;
|
||||
// TODO: don't special case this
|
||||
if (c.visual === "buttonpair") {
|
||||
if (c.visual.builtIn === "buttonpair") {
|
||||
quant = 2;
|
||||
}
|
||||
let cmp = mkCmpDiv(c.visual, {
|
||||
@ -476,7 +466,8 @@ namespace pxsim.instructions {
|
||||
left: QUANT_LBL(quant),
|
||||
leftSize: WIRE_QUANT_LBL_SIZE,
|
||||
wireClr: clr,
|
||||
cmpScale: PARTS_WIRE_SCALE
|
||||
cmpScale: PARTS_WIRE_SCALE,
|
||||
crocClips: props.boardDef.useCrocClips
|
||||
})
|
||||
addClass(cmp, "partslist-wire");
|
||||
panel.appendChild(cmp);
|
||||
@ -509,7 +500,7 @@ namespace pxsim.instructions {
|
||||
let wires = (props.stepToWires[step] || []);
|
||||
let mkLabel = (loc: Loc) => {
|
||||
if (loc.type === "breadboard") {
|
||||
let [row, col] = (<BBLoc>loc).rowCol;
|
||||
let {row, col} = (<BBLoc>loc);
|
||||
return `(${row},${col})`
|
||||
} else
|
||||
return (<BoardLoc>loc).pin;
|
||||
@ -521,24 +512,31 @@ namespace pxsim.instructions {
|
||||
bot: mkLabel(w.start),
|
||||
botSize: LOC_LBL_SIZE,
|
||||
wireClr: w.color,
|
||||
cmpHeight: REQ_WIRE_HEIGHT
|
||||
cmpHeight: REQ_WIRE_HEIGHT,
|
||||
crocClips: props.boardDef.useCrocClips
|
||||
})
|
||||
addClass(cmp, "cmp-div");
|
||||
reqsDiv.appendChild(cmp);
|
||||
});
|
||||
let cmps = (props.stepToCmps[step] || []);
|
||||
cmps.forEach(c => {
|
||||
let l: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn}`];
|
||||
let locs = [l];
|
||||
if (c.visual === "buttonpair") {
|
||||
let locs: BBLoc[];
|
||||
if (c.visual.builtIn === "buttonpair") {
|
||||
//TODO: don't special case this
|
||||
let l2: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn + 3}`];
|
||||
locs.push(l2);
|
||||
locs = [c.breadboardConnections[0], c.breadboardConnections[2]]
|
||||
} else {
|
||||
locs = [c.breadboardConnections[0]];
|
||||
}
|
||||
locs.forEach((l, i) => {
|
||||
let [row, col] = l;
|
||||
let topLbl: string;
|
||||
if (l) {
|
||||
let {row, col} = l;
|
||||
topLbl = `(${row},${col})`;
|
||||
} else {
|
||||
topLbl = "";
|
||||
}
|
||||
let cmp = mkCmpDiv(c.visual, {
|
||||
top: `(${row},${col})`,
|
||||
top: topLbl,
|
||||
topSize: LOC_LBL_SIZE,
|
||||
cmpHeight: REQ_CMP_HEIGHT,
|
||||
cmpScale: REQ_CMP_SCALE
|
||||
@ -560,7 +558,6 @@ namespace pxsim.instructions {
|
||||
return [panel, props];
|
||||
}
|
||||
function mkFinalPanel(props: BoardProps) {
|
||||
const BACK_PAGE_BOARD_WIDTH = PANEL_WIDTH - 20;
|
||||
|
||||
let panel = mkPanel();
|
||||
addClass(panel, "back-panel");
|
||||
@ -587,8 +584,8 @@ namespace pxsim.instructions {
|
||||
if (tsCode) {
|
||||
//we use the docs renderer to decompile the code to blocks and render it
|
||||
//TODO: render the blocks code directly
|
||||
let md =
|
||||
`\`\`\`blocks
|
||||
let md =
|
||||
`\`\`\`blocks
|
||||
${tsCode}
|
||||
\`\`\`
|
||||
\`\`\`package
|
||||
@ -596,9 +593,9 @@ ${tsPackage}
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
pxtdocs.requireMarked = function() { return (<any>window).marked; }
|
||||
pxtdocs.requireMarked = function () { return (<any>window).marked; }
|
||||
pxtrunner.renderMarkdownAsync(codeContainerDiv, md)
|
||||
.done(function() {
|
||||
.done(function () {
|
||||
let codeSvg = $("#proj-code-container svg");
|
||||
if (codeSvg.length > 0) {
|
||||
//code rendered successfully as blocks
|
||||
@ -620,6 +617,9 @@ ${tsPackage}
|
||||
let parts = (getQsVal("parts") || "").split(" ");
|
||||
parts.sort();
|
||||
|
||||
// parts definitions
|
||||
let partDefinitions = JSON.parse(getQsVal("partdefs") || "{}") as pxsim.Map<PartDefinition>
|
||||
|
||||
//fn args
|
||||
let fnArgs = JSON.parse((getQsVal("fnArgs") || "{}"));
|
||||
|
||||
@ -637,7 +637,7 @@ ${tsPackage}
|
||||
style.textContent += STYLE;
|
||||
|
||||
const boardDef = CURRENT_BOARD;
|
||||
const cmpDefs = PART_DEFINITIONS;
|
||||
const cmpDefs = partDefinitions;
|
||||
|
||||
//props
|
||||
let dummyBreadboard = new visuals.Breadboard({});
|
||||
@ -646,8 +646,8 @@ ${tsPackage}
|
||||
activeComponents.sort();
|
||||
let props = mkBoardProps({
|
||||
boardDef: boardDef,
|
||||
cmpDefs: cmpDefs,
|
||||
cmpList: activeComponents,
|
||||
partDefs: cmpDefs,
|
||||
partsList: activeComponents,
|
||||
fnArgs: fnArgs,
|
||||
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
|
||||
});
|
@ -147,7 +147,7 @@ namespace pxsim.visuals {
|
||||
const pin3Vmid = pins4onMids[13] + bigPinWidth / 2.0;
|
||||
const pinGNDmid = pins4onMids[pins4onMids.length - 1] + bigPinWidth / 2.0;
|
||||
const pinGND2mid = pinGNDmid + bigPinWidth / 2.0;
|
||||
const pinMids = [pin0mid, pin1mid, pin2mid, pin3mid].concat(pins4onXs).concat([pinGNDmid, pin3Vmid, pinGND2mid]);
|
||||
const pinMids = [pin0mid, pin1mid, pin2mid, pin3mid].concat(pins4onMids).concat([pinGNDmid, pin3Vmid, pinGND2mid]);
|
||||
const pinNames = [
|
||||
"P0", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10",
|
||||
"P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20",
|
||||
@ -226,18 +226,6 @@ namespace pxsim.visuals {
|
||||
wireframe?: boolean;
|
||||
}
|
||||
|
||||
const pointerEvents = !!(window as any).PointerEvent ? {
|
||||
up: "pointerup",
|
||||
down: "pointerdown",
|
||||
move: "pointermove",
|
||||
leave: "pointerleave"
|
||||
} : {
|
||||
up: "mouseup",
|
||||
down: "mousedown",
|
||||
move: "mousemove",
|
||||
leave: "mouseleave"
|
||||
};
|
||||
|
||||
export class MicrobitBoardSvg implements BoardView {
|
||||
public element: SVGSVGElement;
|
||||
private style: SVGStyleElement;
|
||||
|
3
sim/public/parts/.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
# don't check in until OSS request is approved
|
||||
sparkfun-*
|
||||
raspberrypi-*
|
||||
arduino-*
|
||||
arduino-*
|
||||
max6675*
|
||||
|
@ -96,9 +96,9 @@
|
||||
|
||||
.organization {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
height: 2rem;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -112,23 +112,23 @@
|
||||
|
||||
#front-panel .board-svg {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
width: 140px;
|
||||
top: 8rem;
|
||||
left: 2rem;
|
||||
width: 300px;
|
||||
top: 16rem;
|
||||
}
|
||||
|
||||
#proj-title {
|
||||
top: 20px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
font-size: 70px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#proj-code {
|
||||
width: 140px;
|
||||
height: 200px;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 8rem;
|
||||
right: 2rem;
|
||||
top: 16rem;
|
||||
}
|
||||
#proj-code-container {
|
||||
width: 100%;
|
||||
|
240
sim/simlib.ts
@ -1,240 +0,0 @@
|
||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export type BBRowCol = [/*row*/string, /*column*/string];
|
||||
export type BoardPin = string;
|
||||
export interface BBLoc {type: "breadboard", rowCol: BBRowCol};
|
||||
export interface BoardLoc {type: "dalboard", pin: BoardPin};
|
||||
export type Loc = BBLoc | BoardLoc;
|
||||
|
||||
export function initRuntimeWithDalBoard() {
|
||||
U.assert(!runtime.board);
|
||||
let b = new DalBoard();
|
||||
runtime.board = b;
|
||||
}
|
||||
if (!pxsim.initCurrentRuntime) {
|
||||
pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
|
||||
}
|
||||
|
||||
export function board() {
|
||||
return runtime.board as DalBoard;
|
||||
}
|
||||
|
||||
export function mkRange(a: number, b: number): number[] {
|
||||
let res: number[] = [];
|
||||
for (; a < b; a++)
|
||||
res.push(a);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function parseQueryString(): (key: string) => string {
|
||||
let qs = window.location.search.substring(1);
|
||||
let getQsVal = (key: string) => decodeURIComponent((qs.split(`${key}=`)[1] || "").split("&")[0] || "").replace(/\+/g, " ");
|
||||
return getQsVal;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export interface IPointerEvents {
|
||||
up: string,
|
||||
down: string,
|
||||
move: string,
|
||||
leave: string
|
||||
}
|
||||
|
||||
export const pointerEvents: IPointerEvents = !!(window as any).PointerEvent ? {
|
||||
up: "pointerup",
|
||||
down: "pointerdown",
|
||||
move: "pointermove",
|
||||
leave: "pointerleave"
|
||||
} : {
|
||||
up: "mouseup",
|
||||
down: "mousedown",
|
||||
move: "mousemove",
|
||||
leave: "mouseleave"
|
||||
};
|
||||
|
||||
export function translateEl(el: SVGElement, xy: [number, number]) {
|
||||
//TODO append translation instead of replacing the full transform
|
||||
svg.hydrate(el, {transform: `translate(${xy[0]} ${xy[1]})`});
|
||||
}
|
||||
|
||||
export interface ComposeOpts {
|
||||
el1: SVGAndSize<SVGSVGElement>,
|
||||
scaleUnit1: number,
|
||||
el2: SVGAndSize<SVGSVGElement>,
|
||||
scaleUnit2: number,
|
||||
margin: [number, number, number, number],
|
||||
middleMargin: number,
|
||||
maxWidth?: string,
|
||||
maxHeight?: string,
|
||||
}
|
||||
export interface ComposeResult {
|
||||
host: SVGSVGElement,
|
||||
scaleUnit: number,
|
||||
under: SVGGElement,
|
||||
over: SVGGElement,
|
||||
edges: number[],
|
||||
toHostCoord1: (xy: Coord) => Coord,
|
||||
toHostCoord2: (xy: Coord) => Coord,
|
||||
}
|
||||
export function composeSVG(opts: ComposeOpts): ComposeResult {
|
||||
let [a, b] = [opts.el1, opts.el2];
|
||||
U.assert(a.x == 0 && a.y == 0 && b.x == 0 && b.y == 0, "el1 and el2 x,y offsets not supported");
|
||||
let setXY = (e: SVGSVGElement, x: number, y: number) => svg.hydrate(e, {x: x, y: y});
|
||||
let setWH = (e: SVGSVGElement, w: string, h: string) => {
|
||||
if (w)
|
||||
svg.hydrate(e, {width: w});
|
||||
if (h)
|
||||
svg.hydrate(e, {height: h});
|
||||
}
|
||||
let setWHpx = (e: SVGSVGElement, w: number, h: number) => svg.hydrate(e, {width: `${w}px`, height: `${h}px`});
|
||||
let scaleUnit = opts.scaleUnit2;
|
||||
let aScalar = opts.scaleUnit2 / opts.scaleUnit1;
|
||||
let bScalar = 1.0;
|
||||
let aw = a.w * aScalar;
|
||||
let ah = a.h * aScalar;
|
||||
setWHpx(a.el, aw, ah);
|
||||
let bw = b.w * bScalar;
|
||||
let bh = b.h * bScalar;
|
||||
setWHpx(b.el, bw, bh);
|
||||
let [mt, mr, mb, ml] = opts.margin;
|
||||
let mm = opts.middleMargin;
|
||||
let innerW = Math.max(aw, bw);
|
||||
let ax = mr + (innerW - aw) / 2.0;
|
||||
let ay = mt;
|
||||
setXY(a.el, ax, ay);
|
||||
let bx = mr + (innerW - bw) / 2.0;
|
||||
let by = ay + ah + mm;
|
||||
setXY(b.el, bx, by);
|
||||
let edges = [ay, ay + ah, by, by + bh];
|
||||
let w = mr + innerW + ml;
|
||||
let h = mt + ah + mm + bh + mb;
|
||||
let host = <SVGSVGElement>svg.elt("svg", {
|
||||
"version": "1.0",
|
||||
"viewBox": `0 0 ${w} ${h}`,
|
||||
"class": `sim-bb`,
|
||||
});
|
||||
setWH(host, opts.maxWidth, opts.maxHeight);
|
||||
setXY(host, 0, 0);
|
||||
let under = <SVGGElement>svg.child(host, "g");
|
||||
host.appendChild(a.el);
|
||||
host.appendChild(b.el);
|
||||
let over = <SVGGElement>svg.child(host, "g");
|
||||
let toHostCoord1 = (xy: Coord): Coord => {
|
||||
let [x, y] = xy;
|
||||
return [x * aScalar + ax, y * aScalar + ay];
|
||||
};
|
||||
let toHostCoord2 = (xy: Coord): Coord => {
|
||||
let [x, y] = xy;
|
||||
return [x * bScalar + bx, y * bScalar + by];
|
||||
};
|
||||
return {
|
||||
under: under,
|
||||
over: over,
|
||||
host: host,
|
||||
edges: edges,
|
||||
scaleUnit: scaleUnit,
|
||||
toHostCoord1: toHostCoord1,
|
||||
toHostCoord2: toHostCoord2,
|
||||
};
|
||||
}
|
||||
|
||||
export function mkScaleFn(originUnit: number, targetUnit: number): (n: number) => number {
|
||||
return (n: number) => n * (targetUnit / originUnit);
|
||||
}
|
||||
export interface MkImageOpts {
|
||||
image: string,
|
||||
width: number,
|
||||
height: number,
|
||||
imageUnitDist: number,
|
||||
targetUnitDist: number
|
||||
}
|
||||
export function mkImageSVG(opts: MkImageOpts): SVGAndSize<SVGImageElement> {
|
||||
let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist);
|
||||
let w = scaleFn(opts.width);
|
||||
let h = scaleFn(opts.height);
|
||||
let img = <SVGImageElement>svg.elt("image", {
|
||||
width: w,
|
||||
height: h,
|
||||
"href": `${opts.image}`
|
||||
});
|
||||
return {el: img, w: w, h: h, x: 0, y: 0};
|
||||
}
|
||||
|
||||
export type Coord = [number, number];
|
||||
export function findDistSqrd(a: Coord, b: Coord): number {
|
||||
let x = a[0] - b[0];
|
||||
let y = a[1] - b[1];
|
||||
return x * x + y * y;
|
||||
}
|
||||
export function findClosestCoordIdx(a: Coord, bs: Coord[]): number {
|
||||
let dists = bs.map(b => findDistSqrd(a, b));
|
||||
let minIdx = dists.reduce((prevIdx, currDist, currIdx, arr) => {
|
||||
return currDist < arr[prevIdx] ? currIdx : prevIdx;
|
||||
}, 0);
|
||||
return minIdx;
|
||||
}
|
||||
|
||||
export interface IBoardComponent<T> {
|
||||
style: string,
|
||||
element: SVGElement,
|
||||
defs: SVGElement[],
|
||||
init(bus: EventBus, state: T, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void, //NOTE: constructors not supported in interfaces
|
||||
moveToCoord(xy: Coord): void,
|
||||
updateState(): void,
|
||||
updateTheme(): void,
|
||||
}
|
||||
|
||||
export function mkTxt(cx: number, cy: number, size: number, rot: number, txt: string, txtXOffFactor?: number, txtYOffFactor?: number): SVGTextElement {
|
||||
let el = <SVGTextElement>svg.elt("text")
|
||||
//HACK: these constants (txtXOffFactor, txtYOffFactor) tweak the way this algorithm knows how to center the text
|
||||
txtXOffFactor = txtXOffFactor || -0.33333;
|
||||
txtYOffFactor = txtYOffFactor || 0.3;
|
||||
const xOff = txtXOffFactor * size * txt.length;
|
||||
const yOff = txtYOffFactor * size;
|
||||
svg.hydrate(el, {style: `font-size:${size}px;`,
|
||||
transform: `translate(${cx} ${cy}) rotate(${rot}) translate(${xOff} ${yOff})` });
|
||||
svg.addClass(el, "noselect");
|
||||
el.textContent = txt;
|
||||
return el;
|
||||
}
|
||||
|
||||
export type WireColor =
|
||||
"black" | "white" | "gray" | "purple" | "blue" | "green" | "yellow" | "orange" | "red" | "brown";
|
||||
export const WIRE_COLOR_MAP: Map<string> = {
|
||||
black: "#514f4d",
|
||||
white: "#fcfdfc",
|
||||
gray: "#acabab",
|
||||
purple: "#a772a1",
|
||||
blue: "#01a6e8",
|
||||
green: "#3cce73",
|
||||
yellow: "#ece600",
|
||||
orange: "#fdb262",
|
||||
red: "#f44f43",
|
||||
brown: "#c89764",
|
||||
}
|
||||
export function mapWireColor(clr: WireColor | string): string {
|
||||
return WIRE_COLOR_MAP[clr] || clr;
|
||||
}
|
||||
|
||||
export interface SVGAndSize<T extends SVGElement> {
|
||||
el: T,
|
||||
y: number,
|
||||
x: number,
|
||||
w: number,
|
||||
h: number
|
||||
};
|
||||
export type SVGElAndSize = SVGAndSize<SVGElement>;
|
||||
|
||||
export const PIN_DIST = 15;
|
||||
|
||||
export interface BoardView {
|
||||
getView(): SVGAndSize<SVGSVGElement>;
|
||||
getCoord(pinNm: string): Coord;
|
||||
getPinDist(): number;
|
||||
highlightPin(pinNm: string): void;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
namespace pxsim.input {
|
||||
export function onButtonPressed(button: number, handler: RefAction): void {
|
||||
let b = board().buttonPairState;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
||||
if (button == b.props.ID_BUTTON_AB && !b.usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
@ -10,32 +10,12 @@ namespace pxsim.input {
|
||||
|
||||
export function buttonIsPressed(button: number): boolean {
|
||||
let b = board().buttonPairState;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
||||
if (button == b.abBtn.id && !b.usesButtonAB) {
|
||||
b.usesButtonAB = true;
|
||||
runtime.queueDisplayUpdate();
|
||||
}
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_A) return b.aBtn.pressed;
|
||||
if (button == DAL.MICROBIT_ID_BUTTON_B) return b.bBtn.pressed;
|
||||
if (button == b.aBtn.id) return b.aBtn.pressed;
|
||||
if (button == b.bBtn.id) return b.bBtn.pressed;
|
||||
return b.abBtn.pressed || (b.aBtn.pressed && b.bBtn.pressed);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
export class Button {
|
||||
constructor(public id: number) { }
|
||||
pressed: boolean;
|
||||
}
|
||||
|
||||
export class ButtonPairState {
|
||||
usesButtonAB: boolean = false;
|
||||
aBtn: Button;
|
||||
bBtn: Button;
|
||||
abBtn: Button;
|
||||
|
||||
constructor() {
|
||||
this.aBtn = new Button(DAL.MICROBIT_ID_BUTTON_A);
|
||||
this.bBtn = new Button(DAL.MICROBIT_ID_BUTTON_B);
|
||||
this.abBtn = new Button(DAL.MICROBIT_ID_BUTTON_AB);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,11 +12,4 @@ namespace pxsim.input {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
export class CompassState {
|
||||
usesHeading = false;
|
||||
heading = 90;
|
||||
}
|
||||
}
|
@ -116,72 +116,6 @@ namespace pxsim {
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
export interface AnimationOptions {
|
||||
interval: number;
|
||||
// false means last frame
|
||||
frame: () => boolean;
|
||||
whenDone?: (cancelled: boolean) => void;
|
||||
}
|
||||
|
||||
export class AnimationQueue {
|
||||
private queue: AnimationOptions[] = [];
|
||||
private process: () => void;
|
||||
|
||||
constructor(private runtime: Runtime) {
|
||||
this.process = () => {
|
||||
let top = this.queue[0]
|
||||
if (!top) return
|
||||
if (this.runtime.dead) return
|
||||
runtime = this.runtime
|
||||
let res = top.frame()
|
||||
runtime.queueDisplayUpdate()
|
||||
runtime.maybeUpdateDisplay()
|
||||
if (res === false) {
|
||||
this.queue.shift();
|
||||
// if there is already something in the queue, start processing
|
||||
if (this.queue[0])
|
||||
setTimeout(this.process, this.queue[0].interval)
|
||||
// this may push additional stuff
|
||||
top.whenDone(false);
|
||||
} else {
|
||||
setTimeout(this.process, top.interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public cancelAll() {
|
||||
let q = this.queue
|
||||
this.queue = []
|
||||
for (let a of q) {
|
||||
a.whenDone(true)
|
||||
}
|
||||
}
|
||||
|
||||
public cancelCurrent() {
|
||||
let top = this.queue[0]
|
||||
if (top) {
|
||||
this.queue.shift();
|
||||
top.whenDone(true);
|
||||
}
|
||||
}
|
||||
|
||||
public enqueue(anim: AnimationOptions) {
|
||||
if (!anim.whenDone) anim.whenDone = () => { };
|
||||
this.queue.push(anim)
|
||||
// we start processing when the queue goes from 0 to 1
|
||||
if (this.queue.length == 1)
|
||||
this.process()
|
||||
}
|
||||
|
||||
public executeAsync(anim: AnimationOptions) {
|
||||
U.assert(!anim.whenDone)
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
anim.whenDone = resolve
|
||||
this.enqueue(anim)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.images {
|
||||
@ -195,26 +129,24 @@ namespace pxsim.images {
|
||||
|
||||
namespace pxsim.ImageMethods {
|
||||
export function showImage(leds: Image, offset: number) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
pxtrt.nullCheck(leds)
|
||||
leds.copyTo(offset, 5, board().ledMatrixState.image, 0)
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function plotImage(leds: Image, offset: number): void {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
pxtrt.nullCheck(leds)
|
||||
leds.copyTo(offset, 5, board().ledMatrixState.image, 0)
|
||||
runtime.queueDisplayUpdate()
|
||||
}
|
||||
|
||||
export function height(leds: Image): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
pxtrt.nullCheck(leds)
|
||||
return Image.height;
|
||||
}
|
||||
|
||||
export function width(leds: Image): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
pxtrt.nullCheck(leds)
|
||||
return leds.width;
|
||||
}
|
||||
|
||||
@ -227,35 +159,32 @@ namespace pxsim.ImageMethods {
|
||||
}
|
||||
|
||||
export function pixel(leds: Image, x: number, y: number): number {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
pxtrt.nullCheck(leds)
|
||||
return leds.get(x, y);
|
||||
}
|
||||
|
||||
export function setPixel(leds: Image, x: number, y: number, v: number) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
pxtrt.nullCheck(leds)
|
||||
leds.set(x, y, v);
|
||||
}
|
||||
|
||||
export function clear(leds: Image) {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
pxtrt.nullCheck(leds)
|
||||
leds.clear();
|
||||
}
|
||||
|
||||
export function setPixelBrightness(i: Image, x: number, y: number, b: number) {
|
||||
if (!i) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
pxtrt.nullCheck(i)
|
||||
i.set(x, y, b);
|
||||
}
|
||||
|
||||
export function pixelBrightness(i: Image, x: number, y: number): number {
|
||||
if (!i) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
|
||||
pxtrt.nullCheck(i)
|
||||
return i.get(x, y);
|
||||
}
|
||||
|
||||
export function scrollImage(leds: Image, stride: number, interval: number): void {
|
||||
if (!leds) panic(PanicCode.MICROBIT_NULL_DEREFERENCE);
|
||||
pxtrt.nullCheck(leds)
|
||||
if (stride == 0) stride = 1;
|
||||
|
||||
let cb = getResume();
|
||||
|
@ -1,10 +1,3 @@
|
||||
namespace pxsim {
|
||||
export class LightSensorState {
|
||||
usesLightLevel = false;
|
||||
lightLevel = 128;
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.input {
|
||||
export function lightLevel(): number {
|
||||
let b = board().lightSensorState;
|
||||
|
@ -19,101 +19,12 @@ namespace pxsim {
|
||||
|
||||
export function panic(code: number) {
|
||||
console.log("PANIC:", code)
|
||||
led.setBrightness(255);
|
||||
let img = board().ledMatrixState.image;
|
||||
img.clear();
|
||||
img.set(0, 4, 255);
|
||||
img.set(1, 3, 255);
|
||||
img.set(2, 3, 255);
|
||||
img.set(3, 3, 255);
|
||||
img.set(4, 4, 255);
|
||||
img.set(0, 0, 255);
|
||||
img.set(1, 0, 255);
|
||||
img.set(0, 1, 255);
|
||||
img.set(1, 1, 255);
|
||||
img.set(3, 0, 255);
|
||||
img.set(4, 0, 255);
|
||||
img.set(3, 1, 255);
|
||||
img.set(4, 1, 255);
|
||||
runtime.updateDisplay();
|
||||
|
||||
throw new Error("PANIC " + code)
|
||||
}
|
||||
|
||||
export namespace AudioContextManager {
|
||||
let _context: any; // AudioContext
|
||||
let _vco: any; // OscillatorNode;
|
||||
let _vca: any; // GainNode;
|
||||
|
||||
function context(): any {
|
||||
if (!_context) _context = freshContext();
|
||||
return _context;
|
||||
}
|
||||
|
||||
function freshContext(): any {
|
||||
(<any>window).AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
||||
if ((<any>window).AudioContext) {
|
||||
try {
|
||||
// this call my crash.
|
||||
// SyntaxError: audio resources unavailable for AudioContext construction
|
||||
return new (<any>window).AudioContext();
|
||||
} catch (e) { }
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function stop() {
|
||||
if (_vca) _vca.gain.value = 0;
|
||||
}
|
||||
|
||||
export function tone(frequency: number, gain: number) {
|
||||
if (frequency <= 0) return;
|
||||
let ctx = context();
|
||||
if (!ctx) return;
|
||||
|
||||
gain = Math.max(0, Math.min(1, gain));
|
||||
if (!_vco) {
|
||||
try {
|
||||
_vco = ctx.createOscillator();
|
||||
_vca = ctx.createGain();
|
||||
_vco.connect(_vca);
|
||||
_vca.connect(ctx.destination);
|
||||
_vca.gain.value = gain;
|
||||
_vco.start(0);
|
||||
} catch (e) {
|
||||
_vco = undefined;
|
||||
_vca = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_vco.frequency.value = frequency;
|
||||
_vca.gain.value = gain;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RuntimeOptions {
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export class EventBus {
|
||||
private queues: Map<EventQueue<number>> = {};
|
||||
|
||||
constructor(private runtime: Runtime) { }
|
||||
|
||||
listen(id: number, evid: number, handler: RefAction) {
|
||||
let k = id + ":" + evid;
|
||||
let queue = this.queues[k];
|
||||
if (!queue) queue = this.queues[k] = new EventQueue<number>(this.runtime);
|
||||
queue.handler = handler;
|
||||
}
|
||||
|
||||
queue(id: number, evid: number, value: number = 0) {
|
||||
let k = id + ":" + evid;
|
||||
let queue = this.queues[k];
|
||||
if (queue) queue.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.basic {
|
||||
|
@ -2,8 +2,8 @@ namespace pxsim.visuals {
|
||||
export interface BoardHostOpts {
|
||||
state: DalBoard,
|
||||
boardDef: BoardDefinition,
|
||||
cmpsList?: string[],
|
||||
cmpDefs: Map<PartDefinition>,
|
||||
partsList?: string[],
|
||||
partDefs: Map<PartDefinition>,
|
||||
fnArgs: any,
|
||||
forceBreadboard?: boolean,
|
||||
maxWidth?: string,
|
||||
@ -11,13 +11,15 @@ namespace pxsim.visuals {
|
||||
wireframe?: boolean
|
||||
}
|
||||
export class BoardHost {
|
||||
private components: IBoardComponent<any>[] = [];
|
||||
private parts: IBoardPart<any>[] = [];
|
||||
private wireFactory: WireFactory;
|
||||
private breadboard: Breadboard;
|
||||
private fromBBCoord: (xy: Coord) => Coord;
|
||||
private fromMBCoord: (xy: Coord) => Coord;
|
||||
private boardView: BoardView;
|
||||
private view: SVGSVGElement;
|
||||
private partGroup: SVGGElement;
|
||||
private partOverGroup: SVGGElement;
|
||||
private style: SVGStyleElement;
|
||||
private defs: SVGDefsElement;
|
||||
private state: DalBoard;
|
||||
@ -26,7 +28,7 @@ namespace pxsim.visuals {
|
||||
constructor(opts: BoardHostOpts) {
|
||||
this.state = opts.state;
|
||||
let onboardCmps = opts.boardDef.onboardComponents || [];
|
||||
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
|
||||
let activeComponents = (opts.partsList || []).filter(c => onboardCmps.indexOf(c) < 0);
|
||||
activeComponents.sort();
|
||||
this.useCrocClips = opts.boardDef.useCrocClips;
|
||||
|
||||
@ -68,6 +70,8 @@ namespace pxsim.visuals {
|
||||
this.fromMBCoord = composition.toHostCoord1;
|
||||
this.fromBBCoord = composition.toHostCoord2;
|
||||
let pinDist = composition.scaleUnit;
|
||||
this.partGroup = over;
|
||||
this.partOverGroup = <SVGGElement>svg.child(this.view, "g");
|
||||
|
||||
this.style = <SVGStyleElement>svg.child(this.view, "style", {});
|
||||
this.defs = <SVGDefsElement>svg.child(this.view, "defs", {});
|
||||
@ -76,16 +80,18 @@ namespace pxsim.visuals {
|
||||
|
||||
let allocRes = allocateDefinitions({
|
||||
boardDef: opts.boardDef,
|
||||
cmpDefs: opts.cmpDefs,
|
||||
partDefs: opts.partDefs,
|
||||
fnArgs: opts.fnArgs,
|
||||
getBBCoord: this.breadboard.getCoord.bind(this.breadboard),
|
||||
cmpList: activeComponents,
|
||||
partsList: activeComponents,
|
||||
});
|
||||
|
||||
this.addAll(allocRes);
|
||||
} else {
|
||||
let el = this.boardView.getView().el;
|
||||
this.view = el;
|
||||
this.partGroup = <SVGGElement>svg.child(this.view, "g");
|
||||
this.partOverGroup = <SVGGElement>svg.child(this.view, "g");
|
||||
if (opts.maxWidth)
|
||||
svg.hydrate(this.view, { width: opts.maxWidth });
|
||||
if (opts.maxHeight)
|
||||
@ -99,7 +105,7 @@ namespace pxsim.visuals {
|
||||
this.boardView.highlightPin(pinNm);
|
||||
}
|
||||
|
||||
public highlightBreadboardPin(rowCol: BBRowCol) {
|
||||
public highlightBreadboardPin(rowCol: BBLoc) {
|
||||
this.breadboard.highlightLoc(rowCol);
|
||||
}
|
||||
|
||||
@ -120,10 +126,10 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
this.components.forEach(c => c.updateState());
|
||||
this.parts.forEach(c => c.updateState());
|
||||
}
|
||||
|
||||
private getBBCoord(rowCol: BBRowCol) {
|
||||
private getBBCoord(rowCol: BBLoc) {
|
||||
let bbCoord = this.breadboard.getCoord(rowCol);
|
||||
return this.fromBBCoord(bbCoord);
|
||||
}
|
||||
@ -134,7 +140,7 @@ namespace pxsim.visuals {
|
||||
public getLocCoord(loc: Loc): Coord {
|
||||
let coord: Coord;
|
||||
if (loc.type === "breadboard") {
|
||||
let rowCol = (<BBLoc>loc).rowCol;
|
||||
let rowCol = (<BBLoc>loc);
|
||||
coord = this.getBBCoord(rowCol);
|
||||
} else {
|
||||
let pinNm = (<BoardLoc>loc).pin;
|
||||
@ -147,47 +153,62 @@ namespace pxsim.visuals {
|
||||
return coord;
|
||||
}
|
||||
|
||||
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
|
||||
let cmp: IBoardComponent<any> = null;
|
||||
public addPart(partInst: PartInst): IBoardPart<any> {
|
||||
let part: IBoardPart<any> = null;
|
||||
let colOffset = 0;
|
||||
if (typeof cmpDesc.visual === "string") {
|
||||
let builtinVisual = cmpDesc.visual as string;
|
||||
let cnstr = builtinComponentSimVisual[builtinVisual];
|
||||
let stateFn = builtinComponentSimState[builtinVisual];
|
||||
cmp = cnstr();
|
||||
cmp.init(this.state.bus, stateFn(this.state), this.view, cmpDesc.microbitPins, cmpDesc.otherArgs);
|
||||
if (partInst.simulationBehavior) {
|
||||
//TODO: seperate simulation behavior from builtin visual
|
||||
let builtinBehavior = partInst.simulationBehavior;
|
||||
let cnstr = builtinComponentSimVisual[builtinBehavior];
|
||||
let stateFn = builtinComponentSimState[builtinBehavior];
|
||||
part = cnstr();
|
||||
part.init(this.state.bus, stateFn(this.state), this.view, partInst.params);
|
||||
} else {
|
||||
let vis = cmpDesc.visual as PartVisualDefinition;
|
||||
cmp = new GenericPart(vis);
|
||||
colOffset = vis.extraColumnOffset || 0;
|
||||
let vis = partInst.visual as PartVisualDefinition;
|
||||
part = new GenericPart(vis);
|
||||
}
|
||||
this.components.push(cmp);
|
||||
this.view.appendChild(cmp.element);
|
||||
if (cmp.defs)
|
||||
cmp.defs.forEach(d => this.defs.appendChild(d));
|
||||
this.style.textContent += cmp.style || "";
|
||||
let rowCol = <BBRowCol>[`${cmpDesc.breadboardStartRow}`, `${colOffset + cmpDesc.breadboardStartColumn}`];
|
||||
this.parts.push(part);
|
||||
this.partGroup.appendChild(part.element);
|
||||
if (part.overElement)
|
||||
this.partOverGroup.appendChild(part.overElement);
|
||||
if (part.defs)
|
||||
part.defs.forEach(d => this.defs.appendChild(d));
|
||||
this.style.textContent += part.style || "";
|
||||
let colIdx = partInst.startColumnIdx;
|
||||
let rowIdx = partInst.startRowIdx;
|
||||
let row = getRowName(rowIdx);
|
||||
let col = getColumnName(colIdx);
|
||||
let xOffset = partInst.bbFit.xOffset / partInst.visual.pinDistance;
|
||||
let yOffset = partInst.bbFit.yOffset / partInst.visual.pinDistance;
|
||||
let rowCol = <BBLoc>{
|
||||
type: "breadboard",
|
||||
row: row,
|
||||
col: col,
|
||||
xOffset: xOffset,
|
||||
yOffset: yOffset
|
||||
};
|
||||
let coord = this.getBBCoord(rowCol);
|
||||
cmp.moveToCoord(coord);
|
||||
part.moveToCoord(coord);
|
||||
let getCmpClass = (type: string) => `sim-${type}-cmp`;
|
||||
let cls = getCmpClass(name);
|
||||
svg.addClass(cmp.element, cls);
|
||||
svg.addClass(cmp.element, "sim-cmp");
|
||||
cmp.updateTheme();
|
||||
cmp.updateState();
|
||||
return cmp;
|
||||
let cls = getCmpClass(partInst.name);
|
||||
svg.addClass(part.element, cls);
|
||||
svg.addClass(part.element, "sim-cmp");
|
||||
part.updateTheme();
|
||||
part.updateState();
|
||||
return part;
|
||||
}
|
||||
public addWire(inst: WireInst): Wire {
|
||||
return this.wireFactory.addWire(inst.start, inst.end, inst.color, this.useCrocClips);
|
||||
}
|
||||
public addAll(basicWiresAndCmpsAndWires: AllocatorResult) {
|
||||
let {powerWires, components} = basicWiresAndCmpsAndWires;
|
||||
powerWires.forEach(w => this.addWire(w));
|
||||
components.forEach((cAndWs, idx) => {
|
||||
let {component, wires} = cAndWs;
|
||||
wires.forEach(w => this.addWire(w));
|
||||
this.addComponent(component);
|
||||
});
|
||||
public addAll(allocRes: AllocatorResult) {
|
||||
allocRes.partsAndWires.forEach(pAndWs => {
|
||||
let part = pAndWs.part;
|
||||
if (part)
|
||||
this.addPart(part)
|
||||
let wires = pAndWs.wires;
|
||||
if (wires)
|
||||
wires.forEach(w => this.addWire(w));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,649 +0,0 @@
|
||||
namespace pxsim.visuals {
|
||||
// The distance between the center of two pins. This is the constant on which everything else is based.
|
||||
const PIN_DIST = 15;
|
||||
// CSS styling for the breadboard
|
||||
const BLUE = "#1AA5D7";
|
||||
const RED = "#DD4BA0";
|
||||
const BREADBOARD_CSS = `
|
||||
/* bread board */
|
||||
.sim-bb-background {
|
||||
fill:#E0E0E0;
|
||||
}
|
||||
.sim-bb-pin {
|
||||
fill:#999;
|
||||
}
|
||||
.sim-bb-pin-hover {
|
||||
visibility: hidden;
|
||||
pointer-events: all;
|
||||
stroke-width: ${PIN_DIST / 2}px;
|
||||
stroke: transparent;
|
||||
fill: #777;
|
||||
}
|
||||
.sim-bb-pin-hover:hover {
|
||||
visibility: visible;
|
||||
fill:#444;
|
||||
}
|
||||
.sim-bb-group-wire {
|
||||
stroke: #999;
|
||||
stroke-width: ${PIN_DIST / 4}px;
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-bb-pin-group {
|
||||
pointer-events: all;
|
||||
}
|
||||
.sim-bb-label,
|
||||
.sim-bb-label-hover {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
fill:#555;
|
||||
pointer-events: all;
|
||||
stroke-width: 0;
|
||||
cursor: default;
|
||||
}
|
||||
.sim-bb-label-hover {
|
||||
visibility: hidden;
|
||||
fill:#000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.sim-bb-bar {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.sim-bb-blue {
|
||||
fill:${BLUE};
|
||||
stroke:${BLUE}
|
||||
}
|
||||
.sim-bb-red {
|
||||
fill:${RED};
|
||||
stroke:${RED};
|
||||
}
|
||||
.sim-bb-pin-group:hover .sim-bb-pin-hover,
|
||||
.sim-bb-pin-group:hover .sim-bb-group-wire,
|
||||
.sim-bb-pin-group:hover .sim-bb-label-hover {
|
||||
visibility: visible;
|
||||
}
|
||||
.sim-bb-pin-group:hover .sim-bb-label {
|
||||
visibility: hidden;
|
||||
}
|
||||
/* outline mode */
|
||||
.sim-bb-outline .sim-bb-background {
|
||||
stroke-width: ${PIN_DIST / 7}px;
|
||||
fill: #FFF;
|
||||
stroke: #000;
|
||||
}
|
||||
.sim-bb-outline .sim-bb-mid-channel {
|
||||
fill: #FFF;
|
||||
stroke: #888;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
/* grayed out */
|
||||
.grayed .sim-bb-red,
|
||||
.grayed .sim-bb-blue {
|
||||
fill: #BBB;
|
||||
}
|
||||
.grayed .sim-bb-pin {
|
||||
fill: #BBB;
|
||||
}
|
||||
.grayed .sim-bb-label {
|
||||
fill: #BBB;
|
||||
}
|
||||
.grayed .sim-bb-background {
|
||||
stroke: #BBB;
|
||||
}
|
||||
.grayed .sim-bb-group-wire {
|
||||
stroke: #DDD;
|
||||
}
|
||||
/* highlighted */
|
||||
.sim-bb-label.highlight {
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-bb-label-hover.highlight {
|
||||
visibility: visible;
|
||||
}
|
||||
.sim-bb-blue.highlight {
|
||||
fill:${BLUE};
|
||||
}
|
||||
.sim-bb-red.highlight {
|
||||
fill:${RED};
|
||||
}
|
||||
`
|
||||
// Pin rows and coluns
|
||||
const MID_ROWS = 10;
|
||||
const MID_ROW_GAPS = [4, 4];
|
||||
const MID_ROW_AND_GAPS = MID_ROWS + MID_ROW_GAPS.length;
|
||||
const MID_COLS = 30;
|
||||
const BAR_ROWS = 2;
|
||||
const BAR_COLS = 25;
|
||||
const POWER_ROWS = BAR_ROWS * 2;
|
||||
const POWER_COLS = BAR_COLS * 2;
|
||||
const BAR_COL_GAPS = [4, 9, 14, 19];
|
||||
const BAR_COL_AND_GAPS = BAR_COLS + BAR_COL_GAPS.length;
|
||||
// Essential dimensions
|
||||
const WIDTH = PIN_DIST * (MID_COLS + 3);
|
||||
const HEIGHT = PIN_DIST * (MID_ROW_AND_GAPS + POWER_ROWS + 5.5);
|
||||
const MID_RATIO = 2.0 / 3.0;
|
||||
const BAR_RATIO = (1.0 - MID_RATIO) * 0.5;
|
||||
const MID_HEIGHT = HEIGHT * MID_RATIO;
|
||||
const BAR_HEIGHT = HEIGHT * BAR_RATIO;
|
||||
// Pin grids
|
||||
const MID_GRID_WIDTH = (MID_COLS - 1) * PIN_DIST;
|
||||
const MID_GRID_HEIGHT = (MID_ROW_AND_GAPS - 1) * PIN_DIST;
|
||||
const MID_GRID_X = (WIDTH - MID_GRID_WIDTH) / 2.0;
|
||||
const MID_GRID_Y = BAR_HEIGHT + (MID_HEIGHT - MID_GRID_HEIGHT) / 2.0;
|
||||
const BAR_GRID_HEIGHT = (BAR_ROWS - 1) * PIN_DIST;
|
||||
const BAR_GRID_WIDTH = (BAR_COL_AND_GAPS - 1) * PIN_DIST;
|
||||
const BAR_TOP_GRID_X = (WIDTH - BAR_GRID_WIDTH) / 2.0;
|
||||
const BAR_TOP_GRID_Y = (BAR_HEIGHT - BAR_GRID_HEIGHT) / 2.0;
|
||||
const BAR_BOT_GRID_X = BAR_TOP_GRID_X;
|
||||
const BAR_BOT_GRID_Y = BAR_TOP_GRID_Y + BAR_HEIGHT + MID_HEIGHT;
|
||||
// Individual pins
|
||||
const PIN_HOVER_SCALAR = 1.3;
|
||||
const PIN_WIDTH = PIN_DIST / 2.5;
|
||||
const PIN_ROUNDING = PIN_DIST / 7.5;
|
||||
// Labels
|
||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
||||
const PIN_LBL_HOVER_SCALAR = 1.3;
|
||||
const PLUS_LBL_SIZE = PIN_DIST * 1.7;
|
||||
const MINUS_LBL_SIZE = PIN_DIST * 2;
|
||||
const POWER_LBL_OFFSET = PIN_DIST * 0.8;
|
||||
const MINUS_LBL_EXTRA_OFFSET = PIN_DIST * 0.07;
|
||||
const LBL_ROTATION = -90;
|
||||
// Channels
|
||||
const CHANNEL_HEIGHT = PIN_DIST * 1.0;
|
||||
const SMALL_CHANNEL_HEIGHT = PIN_DIST * 0.05;
|
||||
// Background
|
||||
const BACKGROUND_ROUNDING = PIN_DIST * 0.3;
|
||||
|
||||
export interface GridPin {
|
||||
el: SVGElement,
|
||||
hoverEl: SVGElement,
|
||||
cx: number,
|
||||
cy: number,
|
||||
row: string,
|
||||
col: string,
|
||||
group?: string
|
||||
};
|
||||
export interface GridOptions {
|
||||
xOffset?: number,
|
||||
yOffset?: number,
|
||||
rowCount: number,
|
||||
colCount: number,
|
||||
rowStartIdx?: number,
|
||||
colStartIdx?: number,
|
||||
pinDist: number,
|
||||
mkPin: () => SVGElAndSize,
|
||||
mkHoverPin: () => SVGElAndSize,
|
||||
getRowName: (rowIdx: number) => string,
|
||||
getColName: (colIdx: number) => string,
|
||||
getGroupName?: (rowIdx: number, colIdx: number) => string,
|
||||
rowIdxsWithGap?: number[],
|
||||
colIdxsWithGap?: number[],
|
||||
};
|
||||
export interface GridResult {
|
||||
g: SVGGElement,
|
||||
allPins: GridPin[],
|
||||
}
|
||||
export function mkGrid(opts: GridOptions): GridResult {
|
||||
let xOff = opts.xOffset || 0;
|
||||
let yOff = opts.yOffset || 0;
|
||||
let allPins: GridPin[] = [];
|
||||
let grid = <SVGGElement>svg.elt("g");
|
||||
let colIdxOffset = opts.colStartIdx || 0;
|
||||
let rowIdxOffset = opts.rowStartIdx || 0;
|
||||
let copyArr = <T>(arr: T[]): T[] => arr ? arr.slice(0, arr.length) : [];
|
||||
let removeAll = <T>(arr: T[], e: T): number => {
|
||||
let res = 0;
|
||||
let idx: number;
|
||||
while (0 <= (idx = arr.indexOf(e))) {
|
||||
arr.splice(idx, 1);
|
||||
res += 1;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
let rowGaps = 0;
|
||||
let rowIdxsWithGap = copyArr(opts.rowIdxsWithGap)
|
||||
for (let i = 0; i < opts.rowCount; i++) {
|
||||
let colGaps = 0;
|
||||
let colIdxsWithGap = copyArr(opts.colIdxsWithGap)
|
||||
let cy = yOff + i * opts.pinDist + rowGaps * opts.pinDist;
|
||||
let rowIdx = i + rowIdxOffset;
|
||||
for (let j = 0; j < opts.colCount; j++) {
|
||||
let cx = xOff + j * opts.pinDist + colGaps * opts.pinDist;
|
||||
let colIdx = j + colIdxOffset;
|
||||
const addEl = (pin: SVGElAndSize) => {
|
||||
let pinX = cx - pin.w * 0.5;
|
||||
let pinY = cy - pin.h * 0.5;
|
||||
svg.hydrate(pin.el, {x: pinX, y: pinY});
|
||||
grid.appendChild(pin.el);
|
||||
return pin.el;
|
||||
}
|
||||
let el = addEl(opts.mkPin());
|
||||
let hoverEl = addEl(opts.mkHoverPin());
|
||||
let row = opts.getRowName(rowIdx);
|
||||
let col = opts.getColName(colIdx);
|
||||
let group = opts.getGroupName ? opts.getGroupName(rowIdx, colIdx) : null;
|
||||
let gridPin: GridPin = {el: el, hoverEl: hoverEl, cx: cx, cy: cy, row: row, col: col, group: group};
|
||||
allPins.push(gridPin);
|
||||
//column gaps
|
||||
colGaps += removeAll(colIdxsWithGap, colIdx);
|
||||
}
|
||||
//row gaps
|
||||
rowGaps += removeAll(rowIdxsWithGap, rowIdx);
|
||||
}
|
||||
return {g: grid, allPins: allPins};
|
||||
}
|
||||
function mkBBPin(): SVGElAndSize {
|
||||
let el = svg.elt("rect");
|
||||
let width = PIN_WIDTH;
|
||||
svg.hydrate(el, {
|
||||
class: "sim-bb-pin",
|
||||
rx: PIN_ROUNDING,
|
||||
ry: PIN_ROUNDING,
|
||||
width: width,
|
||||
height: width
|
||||
});
|
||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
||||
}
|
||||
function mkBBHoverPin(): SVGElAndSize {
|
||||
let el = svg.elt("rect");
|
||||
let width = PIN_WIDTH * PIN_HOVER_SCALAR;
|
||||
svg.hydrate(el, {
|
||||
class: "sim-bb-pin-hover",
|
||||
rx: PIN_ROUNDING,
|
||||
ry: PIN_ROUNDING,
|
||||
width: width,
|
||||
height: width,
|
||||
});
|
||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
||||
}
|
||||
export interface GridLabel {
|
||||
el: SVGTextElement,
|
||||
hoverEl: SVGTextElement,
|
||||
txt: string,
|
||||
group?: string,
|
||||
};
|
||||
function mkBBLabel(cx: number, cy: number, size: number, rotation: number, txt: string, group: string, extraClasses?: string[]): GridLabel {
|
||||
//lbl
|
||||
let el = mkTxt(cx, cy, size, rotation, txt);
|
||||
svg.addClass(el, "sim-bb-label");
|
||||
if (extraClasses)
|
||||
extraClasses.forEach(c => svg.addClass(el, c));
|
||||
|
||||
//hover lbl
|
||||
let hoverEl = mkTxt(cx, cy, size * PIN_LBL_HOVER_SCALAR, rotation, txt);
|
||||
svg.addClass(hoverEl, "sim-bb-label-hover");
|
||||
if (extraClasses)
|
||||
extraClasses.forEach(c => svg.addClass(hoverEl, c));
|
||||
|
||||
let lbl = {el: el, hoverEl: hoverEl, txt: txt, group: group};
|
||||
return lbl;
|
||||
}
|
||||
interface BBBar {
|
||||
el: SVGRectElement,
|
||||
group?: string
|
||||
};
|
||||
|
||||
export interface BreadboardOpts {
|
||||
wireframe?: boolean,
|
||||
}
|
||||
export class Breadboard {
|
||||
public bb: SVGSVGElement;
|
||||
private styleEl: SVGStyleElement;
|
||||
private defs: SVGDefsElement;
|
||||
|
||||
//truth
|
||||
private allPins: GridPin[] = [];
|
||||
private allLabels: GridLabel[] = [];
|
||||
private allPowerBars: BBBar[] = [];
|
||||
//quick lookup caches
|
||||
private rowColToPin: Map<Map<GridPin>> = {};
|
||||
private rowColToLbls: Map<Map<GridLabel[]>> = {};
|
||||
|
||||
constructor(opts: BreadboardOpts) {
|
||||
this.buildDom();
|
||||
|
||||
if (opts.wireframe)
|
||||
svg.addClass(this.bb, "sim-bb-outline");
|
||||
}
|
||||
|
||||
public updateLocation(x: number, y: number) {
|
||||
svg.hydrate(this.bb, {
|
||||
x: `${x}px`,
|
||||
y: `${y}px`,
|
||||
});
|
||||
}
|
||||
|
||||
public getPin(row: string, col: string): GridPin {
|
||||
let colToPin = this.rowColToPin[row];
|
||||
if (!colToPin)
|
||||
return null;
|
||||
let pin = colToPin[col];
|
||||
if (!pin)
|
||||
return null;
|
||||
return pin;
|
||||
}
|
||||
public getCoord(rowCol: BBRowCol): Coord {
|
||||
let [row, col] = rowCol;
|
||||
let pin = this.getPin(row, col);
|
||||
if (!pin)
|
||||
return null;
|
||||
return [pin.cx, pin.cy];
|
||||
}
|
||||
|
||||
public getPinDist() {
|
||||
return PIN_DIST;
|
||||
}
|
||||
|
||||
private buildDom() {
|
||||
this.bb = <SVGSVGElement>svg.elt("svg", {
|
||||
"version": "1.0",
|
||||
"viewBox": `0 0 ${WIDTH} ${HEIGHT}`,
|
||||
"class": `sim-bb`,
|
||||
"width": WIDTH + "px",
|
||||
"height": HEIGHT + "px",
|
||||
});
|
||||
this.styleEl = <SVGStyleElement>svg.child(this.bb, "style", {});
|
||||
this.styleEl.textContent += BREADBOARD_CSS;
|
||||
this.defs = <SVGDefsElement>svg.child(this.bb, "defs", {});
|
||||
|
||||
//background
|
||||
svg.child(this.bb, "rect", { class: "sim-bb-background", width: WIDTH, height: HEIGHT, rx: BACKGROUND_ROUNDING, ry: BACKGROUND_ROUNDING});
|
||||
|
||||
//mid channel
|
||||
let channelGid = "sim-bb-channel-grad";
|
||||
let channelGrad = <SVGLinearGradientElement>svg.elt("linearGradient")
|
||||
svg.hydrate(channelGrad, { id: channelGid, x1: "0%", y1: "0%", x2: "0%", y2: "100%" });
|
||||
this.defs.appendChild(channelGrad);
|
||||
let channelDark = "#AAA";
|
||||
let channelLight = "#CCC";
|
||||
let stop1 = svg.child(channelGrad, "stop", { offset: "0%", style: `stop-color: ${channelDark};` })
|
||||
let stop2 = svg.child(channelGrad, "stop", { offset: "20%", style: `stop-color: ${channelLight};` })
|
||||
let stop3 = svg.child(channelGrad, "stop", { offset: "80%", style: `stop-color: ${channelLight};` })
|
||||
let stop4 = svg.child(channelGrad, "stop", { offset: "100%", style: `stop-color: ${channelDark};` })
|
||||
|
||||
const mkChannel = (cy: number, h: number, cls?: string) => {
|
||||
let channel = svg.child(this.bb, "rect", { class: `sim-bb-channel ${cls || ""}`, y: cy - h / 2, width: WIDTH, height: h});
|
||||
channel.setAttribute("fill", `url(#${channelGid})`);
|
||||
return channel;
|
||||
}
|
||||
|
||||
mkChannel(BAR_HEIGHT + MID_HEIGHT / 2, CHANNEL_HEIGHT, "sim-bb-mid-channel");
|
||||
mkChannel(BAR_HEIGHT, SMALL_CHANNEL_HEIGHT);
|
||||
mkChannel(BAR_HEIGHT + MID_HEIGHT, SMALL_CHANNEL_HEIGHT);
|
||||
|
||||
//-----pins
|
||||
const getMidTopOrBot = (rowIdx: number) => rowIdx < MID_ROWS / 2.0 ? "b" : "t";
|
||||
const getBarTopOrBot = (colIdx: number) => colIdx < POWER_COLS / 2.0 ? "b" : "t";
|
||||
const alphabet = "abcdefghij".split("").reverse();
|
||||
const getColName = (colIdx: number) => `${colIdx + 1}`;
|
||||
const getMidRowName = (rowIdx: number) => alphabet[rowIdx];
|
||||
const getMidGroupName = (rowIdx: number, colIdx: number) => {
|
||||
let botOrTop = getMidTopOrBot(rowIdx);
|
||||
let colNm = getColName(colIdx);
|
||||
return `${botOrTop}${colNm}`;
|
||||
};
|
||||
const getBarRowName = (rowIdx: number) => rowIdx === 0 ? "-" : "+";
|
||||
const getBarGroupName = (rowIdx: number, colIdx: number) => {
|
||||
let botOrTop = getBarTopOrBot(colIdx);
|
||||
let rowName = getBarRowName(rowIdx);
|
||||
return `${rowName}${botOrTop}`;
|
||||
};
|
||||
|
||||
//mid grid
|
||||
let midGridRes = mkGrid({
|
||||
xOffset: MID_GRID_X,
|
||||
yOffset: MID_GRID_Y,
|
||||
rowCount: MID_ROWS,
|
||||
colCount: MID_COLS,
|
||||
pinDist: PIN_DIST,
|
||||
mkPin: mkBBPin,
|
||||
mkHoverPin: mkBBHoverPin,
|
||||
getRowName: getMidRowName,
|
||||
getColName: getColName,
|
||||
getGroupName: getMidGroupName,
|
||||
rowIdxsWithGap: MID_ROW_GAPS,
|
||||
});
|
||||
let midGridG = midGridRes.g;
|
||||
this.allPins = this.allPins.concat(midGridRes.allPins);
|
||||
|
||||
//bot bar
|
||||
let botBarGridRes = mkGrid({
|
||||
xOffset: BAR_BOT_GRID_X,
|
||||
yOffset: BAR_BOT_GRID_Y,
|
||||
rowCount: BAR_ROWS,
|
||||
colCount: BAR_COLS,
|
||||
pinDist: PIN_DIST,
|
||||
mkPin: mkBBPin,
|
||||
mkHoverPin: mkBBHoverPin,
|
||||
getRowName: getBarRowName,
|
||||
getColName: getColName,
|
||||
getGroupName: getBarGroupName,
|
||||
colIdxsWithGap: BAR_COL_GAPS,
|
||||
});
|
||||
let botBarGridG = botBarGridRes.g;
|
||||
this.allPins = this.allPins.concat(botBarGridRes.allPins);
|
||||
|
||||
//top bar
|
||||
let topBarGridRes = mkGrid({
|
||||
xOffset: BAR_TOP_GRID_X,
|
||||
yOffset: BAR_TOP_GRID_Y,
|
||||
rowCount: BAR_ROWS,
|
||||
colCount: BAR_COLS,
|
||||
colStartIdx: BAR_COLS,
|
||||
pinDist: PIN_DIST,
|
||||
mkPin: mkBBPin,
|
||||
mkHoverPin: mkBBHoverPin,
|
||||
getRowName: getBarRowName,
|
||||
getColName: getColName,
|
||||
getGroupName: getBarGroupName,
|
||||
colIdxsWithGap: BAR_COL_GAPS.map(g => g + BAR_COLS),
|
||||
});
|
||||
let topBarGridG = topBarGridRes.g;
|
||||
this.allPins = this.allPins.concat(topBarGridRes.allPins);
|
||||
|
||||
//tooltip
|
||||
this.allPins.forEach(pin => {
|
||||
let {el, row, col, hoverEl} = pin
|
||||
let title = `(${row},${col})`;
|
||||
svg.hydrate(el, {title: title});
|
||||
svg.hydrate(hoverEl, {title: title});
|
||||
})
|
||||
|
||||
//catalog pins
|
||||
this.allPins.forEach(pin => {
|
||||
let colToPin = this.rowColToPin[pin.row];
|
||||
if (!colToPin)
|
||||
colToPin = this.rowColToPin[pin.row] = {};
|
||||
colToPin[pin.col] = pin;
|
||||
})
|
||||
|
||||
//-----labels
|
||||
const mkBBLabelAtPin = (row: string, col: string, xOffset: number, yOffset: number, txt: string, group?: string): GridLabel => {
|
||||
let size = PIN_LBL_SIZE;
|
||||
let rotation = LBL_ROTATION;
|
||||
let loc = this.getCoord([row, col]);
|
||||
let [cx, cy] = loc;
|
||||
let t = mkBBLabel(cx + xOffset, cy + yOffset, size, rotation, txt, group);
|
||||
return t;
|
||||
}
|
||||
|
||||
//columns
|
||||
for (let colIdx = 0; colIdx < MID_COLS; colIdx++) {
|
||||
let colNm = getColName(colIdx);
|
||||
//top
|
||||
let rowTIdx = 0;
|
||||
let rowTNm = getMidRowName(rowTIdx);
|
||||
let groupT = getMidGroupName(rowTIdx, colIdx);
|
||||
let lblT = mkBBLabelAtPin(rowTNm, colNm, 0, -PIN_DIST, colNm, groupT);
|
||||
this.allLabels.push(lblT);
|
||||
//bottom
|
||||
let rowBIdx = MID_ROWS - 1;
|
||||
let rowBNm = getMidRowName(rowBIdx);
|
||||
let groupB = getMidGroupName(rowBIdx, colIdx);
|
||||
let lblB = mkBBLabelAtPin(rowBNm, colNm, 0, +PIN_DIST, colNm, groupB);
|
||||
this.allLabels.push(lblB);
|
||||
}
|
||||
//rows
|
||||
for (let rowIdx = 0; rowIdx < MID_ROWS; rowIdx++) {
|
||||
let rowNm = getMidRowName(rowIdx);
|
||||
//top
|
||||
let colTIdx = 0;
|
||||
let colTNm = getColName(colTIdx);
|
||||
let lblT = mkBBLabelAtPin(rowNm, colTNm, -PIN_DIST, 0, rowNm);
|
||||
this.allLabels.push(lblT);
|
||||
//top
|
||||
let colBIdx = MID_COLS - 1;
|
||||
let colBNm = getColName(colBIdx);
|
||||
let lblB = mkBBLabelAtPin(rowNm, colBNm, +PIN_DIST, 0, rowNm);
|
||||
this.allLabels.push(lblB);
|
||||
}
|
||||
|
||||
//+- labels
|
||||
let botPowerLabels = [
|
||||
//BL
|
||||
mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, 0), [`sim-bb-blue`]),
|
||||
mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, 0), [`sim-bb-red`]),
|
||||
//BR
|
||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS - 1), [`sim-bb-blue`]),
|
||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS - 1), [`sim-bb-red`]),
|
||||
];
|
||||
this.allLabels = this.allLabels.concat(botPowerLabels);
|
||||
let topPowerLabels = [
|
||||
//TL
|
||||
mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS), [`sim-bb-blue`]),
|
||||
mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS), [`sim-bb-red`]),
|
||||
//TR
|
||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, POWER_COLS - 1), [`sim-bb-blue`]),
|
||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, POWER_COLS - 1), [`sim-bb-red`]),
|
||||
];
|
||||
this.allLabels = this.allLabels.concat(topPowerLabels);
|
||||
|
||||
//catalog lbls
|
||||
let lblNmToLbls: Map<GridLabel[]> = {};
|
||||
this.allLabels.forEach(lbl => {
|
||||
let {el, txt} = lbl;
|
||||
let lbls = lblNmToLbls[txt] = lblNmToLbls[txt] || []
|
||||
lbls.push(lbl);
|
||||
});
|
||||
const isPowerPin = (pin: GridPin) => pin.row === "-" || pin.row === "+";
|
||||
this.allPins.forEach(pin => {
|
||||
let {row, col, group} = pin;
|
||||
let colToLbls = this.rowColToLbls[row] || (this.rowColToLbls[row] = {});
|
||||
let lbls = colToLbls[col] || (colToLbls[col] = []);
|
||||
if (isPowerPin(pin)) {
|
||||
//power pins
|
||||
let isBot = Number(col) <= BAR_COLS;
|
||||
if (isBot)
|
||||
botPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
|
||||
else
|
||||
topPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
|
||||
} else {
|
||||
//mid pins
|
||||
let rowLbls = lblNmToLbls[row];
|
||||
rowLbls.forEach(l => lbls.push(l));
|
||||
let colLbls = lblNmToLbls[col];
|
||||
colLbls.forEach(l => lbls.push(l));
|
||||
}
|
||||
})
|
||||
|
||||
//-----blue & red lines
|
||||
const lnLen = BAR_GRID_WIDTH + PIN_DIST * 1.5;
|
||||
const lnThickness = PIN_DIST / 5.0;
|
||||
const lnYOff = PIN_DIST * 0.6;
|
||||
const lnXOff = (lnLen - BAR_GRID_WIDTH) / 2.0;
|
||||
const mkPowerLine = (x: number, y: number, group: string, cls: string): BBBar => {
|
||||
let ln = <SVGRectElement>svg.elt("rect");
|
||||
svg.hydrate(ln, {
|
||||
class: `sim-bb-bar ${cls}`,
|
||||
x: x,
|
||||
y: y - lnThickness / 2.0,
|
||||
width: lnLen,
|
||||
height: lnThickness});
|
||||
let bar: BBBar = {el: ln, group: group};
|
||||
return bar;
|
||||
}
|
||||
let barLines = [
|
||||
//top
|
||||
mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y - lnYOff, getBarGroupName(0, POWER_COLS - 1), "sim-bb-blue"),
|
||||
mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, POWER_COLS - 1), "sim-bb-red"),
|
||||
//bot
|
||||
mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y - lnYOff, getBarGroupName(0, 0), "sim-bb-blue"),
|
||||
mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, 0), "sim-bb-red"),
|
||||
];
|
||||
this.allPowerBars = this.allPowerBars.concat(barLines);
|
||||
//attach power bars
|
||||
this.allPowerBars.forEach(b => this.bb.appendChild(b.el));
|
||||
|
||||
//-----electrically connected groups
|
||||
//make groups
|
||||
let allGrpNms = this.allPins.map(p => p.group).filter((g, i, a) => a.indexOf(g) == i);
|
||||
let groups: SVGGElement[] = allGrpNms.map(grpNm => {
|
||||
let g = <SVGGElement>svg.elt("g");
|
||||
return g;
|
||||
});
|
||||
groups.forEach(g => svg.addClass(g, "sim-bb-pin-group"));
|
||||
groups.forEach((g, i) => svg.addClass(g, `group-${allGrpNms[i]}`));
|
||||
let grpNmToGroup: Map<SVGGElement> = {};
|
||||
allGrpNms.forEach((g, i) => grpNmToGroup[g] = groups[i]);
|
||||
//group pins and add connecting wire
|
||||
let grpNmToPins: Map<GridPin[]> = {};
|
||||
this.allPins.forEach((p, i) => {
|
||||
let g = p.group;
|
||||
let pins = grpNmToPins[g] || (grpNmToPins[g] = []);
|
||||
pins.push(p);
|
||||
});
|
||||
//connecting wire
|
||||
allGrpNms.forEach(grpNm => {
|
||||
let pins = grpNmToPins[grpNm];
|
||||
let [xs, ys] = [pins.map(p => p.cx), pins.map(p => p.cy)];
|
||||
let minFn = (arr: number[]) => arr.reduce((a, b) => a < b ? a : b);
|
||||
let maxFn = (arr: number[]) => arr.reduce((a, b) => a > b ? a : b);
|
||||
let [minX, maxX, minY, maxY] = [minFn(xs), maxFn(xs), minFn(ys), maxFn(ys)];
|
||||
let wire = svg.elt("rect");
|
||||
let width = Math.max(maxX - minX, 0.0001/*rects with no width aren't displayed*/);
|
||||
let height = Math.max(maxY - minY, 0.0001);
|
||||
svg.hydrate(wire, {x: minX, y: minY, width: width, height: height});
|
||||
svg.addClass(wire, "sim-bb-group-wire")
|
||||
let g = grpNmToGroup[grpNm];
|
||||
g.appendChild(wire);
|
||||
});
|
||||
//group pins
|
||||
this.allPins.forEach(p => {
|
||||
let g = grpNmToGroup[p.group];
|
||||
g.appendChild(p.el);
|
||||
g.appendChild(p.hoverEl);
|
||||
})
|
||||
//group lbls
|
||||
let miscLblGroup = <SVGGElement>svg.elt("g");
|
||||
svg.hydrate(miscLblGroup, {class: "sim-bb-group-misc"});
|
||||
groups.push(miscLblGroup);
|
||||
this.allLabels.forEach(l => {
|
||||
if (l.group) {
|
||||
let g = grpNmToGroup[l.group];
|
||||
g.appendChild(l.el);
|
||||
g.appendChild(l.hoverEl);
|
||||
} else {
|
||||
miscLblGroup.appendChild(l.el);
|
||||
miscLblGroup.appendChild(l.hoverEl);
|
||||
}
|
||||
})
|
||||
|
||||
//attach to bb
|
||||
groups.forEach(g => this.bb.appendChild(g)); //attach to breadboard
|
||||
}
|
||||
|
||||
public getSVGAndSize(): SVGAndSize<SVGSVGElement> {
|
||||
return {el: this.bb, y: 0, x: 0, w: WIDTH, h: HEIGHT};
|
||||
}
|
||||
|
||||
public highlightLoc(rowCol: BBRowCol) {
|
||||
let [row, col] = rowCol;
|
||||
let pin = this.rowColToPin[row][col];
|
||||
let {cx, cy} = pin;
|
||||
let lbls = this.rowColToLbls[row][col];
|
||||
const highlightLbl = (lbl: GridLabel) => {
|
||||
svg.addClass(lbl.el, "highlight");
|
||||
svg.addClass(lbl.hoverEl, "highlight");
|
||||
};
|
||||
lbls.forEach(highlightLbl);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export function mkBtnSvg(xy: Coord): SVGAndSize<SVGGElement> {
|
||||
let [innerCls, outerCls] = ["sim-button", "sim-button-outer"];
|
||||
const tabSize = PIN_DIST / 2.5;
|
||||
const pegR = PIN_DIST / 5;
|
||||
const btnR = PIN_DIST * .8;
|
||||
const pegMargin = PIN_DIST / 8;
|
||||
const plateR = PIN_DIST / 12;
|
||||
|
||||
const pegOffset = pegMargin + pegR;
|
||||
let [x, y] = xy;
|
||||
const left = x - tabSize / 2;
|
||||
const top = y - tabSize / 2;
|
||||
const plateH = 3 * PIN_DIST - tabSize;
|
||||
const plateW = 2 * PIN_DIST + tabSize;
|
||||
const plateL = left;
|
||||
const plateT = top + tabSize;
|
||||
const btnCX = plateL + plateW / 2;
|
||||
const btnCY = plateT + plateH / 2;
|
||||
|
||||
let btng = <SVGGElement>svg.elt("g");
|
||||
//tabs
|
||||
const mkTab = (x: number, y: number) => {
|
||||
svg.child(btng, "rect", { class: "sim-button-tab", x: x, y: y, width: tabSize, height: tabSize})
|
||||
}
|
||||
mkTab(left, top);
|
||||
mkTab(left + 2 * PIN_DIST, top);
|
||||
mkTab(left, top + 3 * PIN_DIST);
|
||||
mkTab(left + 2 * PIN_DIST, top + 3 * PIN_DIST);
|
||||
|
||||
//plate
|
||||
svg.child(btng, "rect", { class: outerCls, x: plateL, y: plateT, rx: plateR, ry: plateR, width: plateW, height: plateH });
|
||||
|
||||
//pegs
|
||||
const mkPeg = (x: number, y: number) => {
|
||||
svg.child(btng, "circle", { class: "sim-button-nut", cx: x, cy: y, r: pegR });
|
||||
}
|
||||
mkPeg(plateL + pegOffset, plateT + pegOffset)
|
||||
mkPeg(plateL + plateW - pegOffset, plateT + pegOffset)
|
||||
mkPeg(plateL + pegOffset, plateT + plateH - pegOffset)
|
||||
mkPeg(plateL + plateW - pegOffset, plateT + plateH - pegOffset)
|
||||
|
||||
//inner btn
|
||||
let innerBtn = svg.child(btng, "circle", { class: innerCls, cx: btnCX, cy: btnCY, r: btnR });
|
||||
|
||||
//return
|
||||
return { el: btng, y: top, x: left, w: plateW, h: plateH + 2 * tabSize };
|
||||
}
|
||||
export const BUTTON_PAIR_STYLE = `
|
||||
.sim-button {
|
||||
pointer-events: none;
|
||||
fill: #000;
|
||||
}
|
||||
.sim-button-outer:active ~ .sim-button,
|
||||
.sim-button-virtual:active {
|
||||
fill: #FFA500;
|
||||
}
|
||||
.sim-button-outer {
|
||||
cursor: pointer;
|
||||
fill: #979797;
|
||||
}
|
||||
.sim-button-outer:hover {
|
||||
stroke:gray;
|
||||
stroke-width: ${PIN_DIST / 5}px;
|
||||
}
|
||||
.sim-button-nut {
|
||||
fill:#000;
|
||||
pointer-events:none;
|
||||
}
|
||||
.sim-button-nut:hover {
|
||||
stroke:${PIN_DIST / 15}px solid #704A4A;
|
||||
}
|
||||
.sim-button-tab {
|
||||
fill:#FFF;
|
||||
pointer-events:none;
|
||||
}
|
||||
.sim-button-virtual {
|
||||
cursor: pointer;
|
||||
fill: rgba(255, 255, 255, 0.6);
|
||||
stroke: rgba(255, 255, 255, 1);
|
||||
stroke-width: ${PIN_DIST / 5}px;
|
||||
}
|
||||
.sim-button-virtual:hover {
|
||||
stroke: rgba(128, 128, 128, 1);
|
||||
}
|
||||
.sim-text-virtual {
|
||||
fill: #000;
|
||||
pointer-events:none;
|
||||
}
|
||||
`;
|
||||
export class ButtonPairView implements IBoardComponent<ButtonPairState> {
|
||||
public element: SVGElement;
|
||||
public defs: SVGElement[];
|
||||
public style = BUTTON_PAIR_STYLE;
|
||||
private state: ButtonPairState;
|
||||
private bus: EventBus;
|
||||
private aBtn: SVGGElement;
|
||||
private bBtn: SVGGElement;
|
||||
private abBtn: SVGGElement;
|
||||
|
||||
public init(bus: EventBus, state: ButtonPairState) {
|
||||
this.state = state;
|
||||
this.bus = bus;
|
||||
this.defs = [];
|
||||
this.element = this.mkBtns();
|
||||
this.updateState();
|
||||
this.attachEvents();
|
||||
}
|
||||
|
||||
public moveToCoord(xy: Coord) {
|
||||
let btnWidth = PIN_DIST * 3;
|
||||
let [x, y] = xy;
|
||||
translateEl(this.aBtn, [x, y])
|
||||
translateEl(this.bBtn, [x + btnWidth, y])
|
||||
translateEl(this.abBtn, [x + PIN_DIST * 1.5, y + PIN_DIST * 4])
|
||||
}
|
||||
|
||||
public updateState() {
|
||||
let stateBtns = [this.state.aBtn, this.state.bBtn, this.state.abBtn];
|
||||
let svgBtns = [this.aBtn, this.bBtn, this.abBtn];
|
||||
|
||||
if (this.state.usesButtonAB && this.abBtn.style.visibility != "visible") {
|
||||
this.abBtn.style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
|
||||
public updateTheme() {}
|
||||
|
||||
private mkBtns() {
|
||||
this.aBtn = mkBtnSvg([0, 0]).el;
|
||||
this.bBtn = mkBtnSvg([0, 0]).el;
|
||||
|
||||
const mkVirtualBtn = () => {
|
||||
const numPins = 2;
|
||||
const w = PIN_DIST * 2.8;
|
||||
const offset = (w - (numPins * PIN_DIST)) / 2;
|
||||
const corner = PIN_DIST / 2;
|
||||
const cx = 0 - offset + w / 2;
|
||||
const cy = cx;
|
||||
const txtSize = PIN_DIST * 1.3;
|
||||
const x = -offset;
|
||||
const y = -offset;
|
||||
const txtXOff = PIN_DIST / 7;
|
||||
const txtYOff = PIN_DIST / 10;
|
||||
|
||||
let btng = <SVGGElement>svg.elt("g");
|
||||
let btn = svg.child(btng, "rect", { class: "sim-button-virtual", x: x, y: y, rx: corner, ry: corner, width: w, height: w});
|
||||
let btnTxt = mkTxt(cx + txtXOff, cy + txtYOff, txtSize, 0, "A+B");
|
||||
svg.addClass(btnTxt, "sim-text")
|
||||
svg.addClass(btnTxt, "sim-text-virtual");
|
||||
btng.appendChild(btnTxt);
|
||||
|
||||
return btng;
|
||||
}
|
||||
|
||||
this.abBtn = mkVirtualBtn();
|
||||
this.abBtn.style.visibility = "hidden";
|
||||
|
||||
let el = svg.elt("g");
|
||||
svg.addClass(el, "sim-buttonpair")
|
||||
el.appendChild(this.aBtn);
|
||||
el.appendChild(this.bBtn);
|
||||
el.appendChild(this.abBtn);
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
private attachEvents() {
|
||||
let btnStates = [this.state.aBtn, this.state.bBtn];
|
||||
let btnSvgs = [this.aBtn, this.bBtn];
|
||||
btnSvgs.forEach((btn, index) => {
|
||||
btn.addEventListener(pointerEvents.down, ev => {
|
||||
btnStates[index].pressed = true;
|
||||
})
|
||||
btn.addEventListener(pointerEvents.leave, ev => {
|
||||
btnStates[index].pressed = false;
|
||||
})
|
||||
btn.addEventListener(pointerEvents.up, ev => {
|
||||
btnStates[index].pressed = false;
|
||||
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
})
|
||||
let updateBtns = (s: boolean) => {
|
||||
btnStates.forEach(b => b.pressed = s)
|
||||
};
|
||||
this.abBtn.addEventListener(pointerEvents.down, ev => {
|
||||
updateBtns(true);
|
||||
})
|
||||
this.abBtn.addEventListener(pointerEvents.leave, ev => {
|
||||
updateBtns(false);
|
||||
})
|
||||
this.abBtn.addEventListener(pointerEvents.up, ev => {
|
||||
updateBtns(false);
|
||||
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_UP);
|
||||
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,306 +0,0 @@
|
||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export const BOARD_SYTLE = `
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Chrome/Safari/Opera */
|
||||
-khtml-user-select: none; /* Konqueror */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
not supported by any browser */
|
||||
}
|
||||
|
||||
.sim-board-pin {
|
||||
fill:#999;
|
||||
stroke:#000;
|
||||
stroke-width:${PIN_DIST / 3.0}px;
|
||||
}
|
||||
.sim-board-pin-lbl {
|
||||
fill: #333;
|
||||
}
|
||||
.gray-cover {
|
||||
fill:#FFF;
|
||||
opacity: 0.7;
|
||||
stroke-width:0;
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-board-pin-hover {
|
||||
visibility: hidden;
|
||||
pointer-events: all;
|
||||
stroke-width:${PIN_DIST / 6.0}px;
|
||||
}
|
||||
.sim-board-pin-hover:hover {
|
||||
visibility: visible;
|
||||
}
|
||||
.sim-board-pin-lbl {
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-board-outline .sim-board-pin-lbl {
|
||||
visibility: visible;
|
||||
}
|
||||
.sim-board-pin-lbl {
|
||||
fill: #555;
|
||||
}
|
||||
.sim-board-pin-lbl-hover {
|
||||
fill: red;
|
||||
}
|
||||
.sim-board-outline .sim-board-pin-lbl-hover {
|
||||
fill: black;
|
||||
}
|
||||
.sim-board-pin-lbl,
|
||||
.sim-board-pin-lbl-hover {
|
||||
font-family:"Lucida Console", Monaco, monospace;
|
||||
pointer-events: all;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.sim-board-pin-lbl-hover {
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-board-outline .sim-board-pin-hover:hover + .sim-board-pin-lbl,
|
||||
.sim-board-pin-lbl.highlight {
|
||||
visibility: hidden;
|
||||
}
|
||||
.sim-board-outline .sim-board-pin-hover:hover + * + .sim-board-pin-lbl-hover,
|
||||
.sim-board-pin-lbl-hover.highlight {
|
||||
visibility: visible;
|
||||
}
|
||||
/* Graying out */
|
||||
.grayed .sim-board-pin-lbl:not(.highlight) {
|
||||
fill: #AAA;
|
||||
}
|
||||
.grayed .sim-board-pin:not(.highlight) {
|
||||
fill:#BBB;
|
||||
stroke:#777;
|
||||
}
|
||||
.grayed .gray-cover {
|
||||
visibility: inherit;
|
||||
}
|
||||
.grayed .sim-cmp:not(.notgrayed) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
/* Highlighting */
|
||||
.sim-board-pin-lbl.highlight {
|
||||
fill: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.sim-board-pin.highlight {
|
||||
fill:#999;
|
||||
stroke:#000;
|
||||
}
|
||||
`;
|
||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
||||
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
|
||||
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
|
||||
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
|
||||
|
||||
export interface GenericBoardProps {
|
||||
visualDef: BoardImageDefinition;
|
||||
wireframe?: boolean;
|
||||
}
|
||||
|
||||
let nextBoardId = 0;
|
||||
export class GenericBoardSvg implements BoardView {
|
||||
private element: SVGSVGElement;
|
||||
private style: SVGStyleElement;
|
||||
private defs: SVGDefsElement;
|
||||
private g: SVGGElement;
|
||||
private background: SVGElement;
|
||||
private width: number;
|
||||
private height: number;
|
||||
private id: number;
|
||||
|
||||
// pins & labels
|
||||
//(truth)
|
||||
private allPins: GridPin[] = [];
|
||||
private allLabels: GridLabel[] = [];
|
||||
//(cache)
|
||||
private pinNmToLbl: Map<GridLabel> = {};
|
||||
private pinNmToPin: Map<GridPin> = {};
|
||||
|
||||
constructor(public props: GenericBoardProps) {
|
||||
//TODO: handle wireframe mode
|
||||
this.id = nextBoardId++;
|
||||
let visDef = props.visualDef;
|
||||
let imgHref = props.wireframe ? visDef.outlineImage : visDef.image;
|
||||
let boardImgAndSize = mkImageSVG({
|
||||
image: imgHref,
|
||||
width: visDef.width,
|
||||
height: visDef.height,
|
||||
imageUnitDist: visDef.pinDist,
|
||||
targetUnitDist: PIN_DIST
|
||||
});
|
||||
let scaleFn = mkScaleFn(visDef.pinDist, PIN_DIST);
|
||||
this.width = boardImgAndSize.w;
|
||||
this.height = boardImgAndSize.h;
|
||||
let img = boardImgAndSize.el;
|
||||
this.element = <SVGSVGElement>svg.elt("svg");
|
||||
svg.hydrate(this.element, {
|
||||
"version": "1.0",
|
||||
"viewBox": `0 0 ${this.width} ${this.height}`,
|
||||
"class": `sim sim-board-id-${this.id}`,
|
||||
"x": "0px",
|
||||
"y": "0px"
|
||||
});
|
||||
if (props.wireframe)
|
||||
svg.addClass(this.element, "sim-board-outline")
|
||||
this.style = <SVGStyleElement>svg.child(this.element, "style", {});
|
||||
this.style.textContent += BOARD_SYTLE;
|
||||
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
|
||||
this.g = <SVGGElement>svg.elt("g");
|
||||
this.element.appendChild(this.g);
|
||||
|
||||
// main board
|
||||
this.g.appendChild(img);
|
||||
this.background = img;
|
||||
svg.hydrate(img, { class: "sim-board" });
|
||||
let backgroundCover = this.mkGrayCover(0, 0, this.width, this.height);
|
||||
this.g.appendChild(backgroundCover);
|
||||
|
||||
// ----- pins
|
||||
const mkSquarePin = (): SVGElAndSize => {
|
||||
let el = svg.elt("rect");
|
||||
let width = SQUARE_PIN_WIDTH;
|
||||
svg.hydrate(el, {
|
||||
class: "sim-board-pin",
|
||||
width: width,
|
||||
height: width,
|
||||
});
|
||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
||||
}
|
||||
const mkSquareHoverPin = (): SVGElAndSize => {
|
||||
let el = svg.elt("rect");
|
||||
let width = SQUARE_PIN_HOVER_WIDTH;
|
||||
svg.hydrate(el, {
|
||||
class: "sim-board-pin-hover",
|
||||
width: width,
|
||||
height: width
|
||||
});
|
||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
||||
}
|
||||
const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
|
||||
let xOffset = scaleFn(pinBlock.x) + PIN_DIST / 2.0;
|
||||
let yOffset = scaleFn(pinBlock.y) + PIN_DIST / 2.0;
|
||||
let rowCount = 1;
|
||||
let colCount = pinBlock.labels.length;
|
||||
let getColName = (colIdx: number) => pinBlock.labels[colIdx];
|
||||
let getRowName = () => `${blockIdx + 1}`
|
||||
let getGroupName = () => pinBlock.labels.join(" ");
|
||||
let gridRes = mkGrid({
|
||||
xOffset: xOffset,
|
||||
yOffset: yOffset,
|
||||
rowCount: rowCount,
|
||||
colCount: colCount,
|
||||
pinDist: PIN_DIST,
|
||||
mkPin: mkSquarePin,
|
||||
mkHoverPin: mkSquareHoverPin,
|
||||
getRowName: getRowName,
|
||||
getColName: getColName,
|
||||
getGroupName: getGroupName,
|
||||
});
|
||||
let pins = gridRes.allPins;
|
||||
let pinsG = gridRes.g;
|
||||
svg.addClass(gridRes.g, "sim-board-pin-group");
|
||||
return gridRes;
|
||||
};
|
||||
let pinBlocks = visDef.pinBlocks.map(mkPinBlockGrid);
|
||||
let pinToBlockDef: PinBlockDefinition[] = [];
|
||||
pinBlocks.forEach((blk, blkIdx) => blk.allPins.forEach((p, pIdx) => {
|
||||
this.allPins.push(p);
|
||||
pinToBlockDef.push(visDef.pinBlocks[blkIdx]);
|
||||
}));
|
||||
//tooltip
|
||||
this.allPins.forEach(p => {
|
||||
let tooltip = p.col;
|
||||
svg.hydrate(p.el, {title: tooltip});
|
||||
svg.hydrate(p.hoverEl, {title: tooltip});
|
||||
});
|
||||
//attach pins
|
||||
this.allPins.forEach(p => {
|
||||
this.g.appendChild(p.el);
|
||||
this.g.appendChild(p.hoverEl);
|
||||
});
|
||||
//catalog pins
|
||||
this.allPins.forEach(p => {
|
||||
this.pinNmToPin[p.col] = p;
|
||||
});
|
||||
|
||||
// ----- labels
|
||||
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string, pos: "above" | "below"): SVGTextElement => {
|
||||
//TODO: extract constants
|
||||
let lblY: number;
|
||||
let lblX: number;
|
||||
|
||||
if (pos === "below") {
|
||||
let lblLen = size * 0.25 * txt.length;
|
||||
lblX = pinX;
|
||||
lblY = pinY + 12 + lblLen;
|
||||
} else {
|
||||
let lblLen = size * 0.32 * txt.length;
|
||||
lblX = pinX;
|
||||
lblY = pinY - 11 - lblLen;
|
||||
}
|
||||
let el = mkTxt(lblX, lblY, size, -90, txt);
|
||||
return el;
|
||||
};
|
||||
const mkLabel = (pinX: number, pinY: number, txt: string, pos: "above" | "below"): GridLabel => {
|
||||
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt, pos);
|
||||
svg.addClass(el, "sim-board-pin-lbl");
|
||||
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt, pos);
|
||||
svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
|
||||
let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
|
||||
return label;
|
||||
}
|
||||
this.allLabels = this.allPins.map((p, pIdx) => {
|
||||
let blk = pinToBlockDef[pIdx];
|
||||
return mkLabel(p.cx, p.cy, p.col, blk.labelPosition);
|
||||
});
|
||||
//attach labels
|
||||
this.allLabels.forEach(l => {
|
||||
this.g.appendChild(l.el);
|
||||
this.g.appendChild(l.hoverEl);
|
||||
});
|
||||
//catalog labels
|
||||
this.allPins.forEach((pin, pinIdx) => {
|
||||
let lbl = this.allLabels[pinIdx];
|
||||
this.pinNmToLbl[pin.col] = lbl;
|
||||
});
|
||||
}
|
||||
|
||||
public getCoord(pinNm: string): Coord {
|
||||
let pin = this.pinNmToPin[pinNm];
|
||||
if (!pin)
|
||||
return null;
|
||||
return [pin.cx, pin.cy];
|
||||
}
|
||||
|
||||
private mkGrayCover(x: number, y: number, w: number, h: number) {
|
||||
let rect = <SVGRectElement>svg.elt("rect");
|
||||
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
public getView(): SVGAndSize<SVGSVGElement> {
|
||||
return {el: this.element, w: this.width, h: this.height, x: 0, y: 0};
|
||||
}
|
||||
|
||||
public getPinDist() {
|
||||
return PIN_DIST;
|
||||
}
|
||||
|
||||
public highlightPin(pinNm: string) {
|
||||
let lbl = this.pinNmToLbl[pinNm];
|
||||
let pin = this.pinNmToPin[pinNm];
|
||||
if (lbl && pin) {
|
||||
svg.addClass(lbl.el, "highlight");
|
||||
svg.addClass(lbl.hoverEl, "highlight");
|
||||
svg.addClass(pin.el, "highlight");
|
||||
svg.addClass(pin.hoverEl, "highlight");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export function mkGenericPartSVG(partVisual: PartVisualDefinition): SVGAndSize<SVGImageElement> {
|
||||
let imgAndSize = mkImageSVG({
|
||||
image: partVisual.image,
|
||||
width: partVisual.width,
|
||||
height: partVisual.height,
|
||||
imageUnitDist: partVisual.pinDist,
|
||||
targetUnitDist: PIN_DIST
|
||||
});
|
||||
return imgAndSize;
|
||||
}
|
||||
|
||||
export class GenericPart implements IBoardComponent<any> {
|
||||
public style: string = "";
|
||||
public element: SVGElement;
|
||||
defs: SVGElement[] = [];
|
||||
|
||||
constructor(partVisual: PartVisualDefinition) {
|
||||
let imgAndSize = mkGenericPartSVG(partVisual);
|
||||
let img = imgAndSize.el;
|
||||
let scaleFn = mkScaleFn(partVisual.pinDist, PIN_DIST);
|
||||
let [pinX, pinY] = partVisual.firstPin;
|
||||
let left = -scaleFn(pinX);
|
||||
let top = -scaleFn(pinY);
|
||||
translateEl(img, [left, top]); // So that 0,0 is on the first pin
|
||||
this.element = svg.elt("g");
|
||||
this.element.appendChild(img);
|
||||
}
|
||||
|
||||
moveToCoord(xy: Coord): void {
|
||||
translateEl(this.element, xy);
|
||||
}
|
||||
|
||||
//unused
|
||||
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void { }
|
||||
updateState(): void { }
|
||||
updateTheme(): void { }
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export function mkLedMatrixSvg(xy: Coord, rows: number, cols: number):
|
||||
@ -68,7 +67,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
`
|
||||
|
||||
export class LedMatrixView implements IBoardComponent<LedMatrixState> {
|
||||
export class LedMatrixView implements IBoardPart<LedMatrixState> {
|
||||
private background: SVGElement;
|
||||
private ledsOuter: SVGElement[];
|
||||
private leds: SVGElement[];
|
||||
|
@ -2,8 +2,6 @@
|
||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/shims.d.ts"/>
|
||||
/// <reference path="../../libs/microbit/enums.d.ts"/>
|
||||
/// <reference path="../state/neopixel.ts"/>
|
||||
/// <reference path="../simlib.ts"/>
|
||||
|
||||
//TODO move to utils
|
||||
namespace pxsim.visuals {
|
||||
@ -59,37 +57,60 @@ namespace pxsim.visuals {
|
||||
const NP_PART_YOFF = -11;
|
||||
const NP_PART_WIDTH = 87.5;
|
||||
const NP_PART_HEIGHT = 190;
|
||||
const NEOPIXEL_PART_IMG = "neopixel.svg";
|
||||
const NEOPIXEL_PART_IMG = `<svg viewBox="-5 -1 53 112" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
|
||||
<rect x="2.5" width="38" height="100" style="fill: rgb(68, 68, 68);"/>
|
||||
<rect x="11.748" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<rect x="20.75" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<rect x="29.75" y="3.2" width="1.391" height="2.553" style="fill: none; stroke-linejoin: round; stroke-width: 3; stroke: rgb(165, 103, 52);"/>
|
||||
<g>
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
<path d="M -7.25 -103.2 L -2.5 -100.003 L -12 -100.003 L -7.25 -103.2 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -12 -103.2 9.5 3.197 0.5 0 1@ad6f5cac"/>
|
||||
<path d="M -16.75 -103.197 L -12 -100 L -21.5 -100 L -16.75 -103.197 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -21.5 -103.197 9.5 3.197 0.5 0 1@07d73149"/>
|
||||
<path d="M -26.25 -103.2 L -21.5 -100.003 L -31 -100.003 L -26.25 -103.2 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -31 -103.2 9.5 3.197 0.5 0 1@54403e2d"/>
|
||||
<path d="M -35.75 -103.197 L -31 -100 L -40.5 -100 L -35.75 -103.197 Z" style="fill: rgb(68, 68, 68);" transform="matrix(-1, 0, 0, -1, 0, 0)" bx:shape="triangle -40.5 -103.197 9.5 3.197 0.5 0 1@21c9b772"/>
|
||||
<g transform="matrix(1, 0, 0, 1, 0.000002, 29.999994)">
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
<g transform="matrix(1, 0, 0, 1, 0.000005, 59.999992)">
|
||||
<rect x="9" y="16.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="22.562" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="9" y="28.563" width="25" height="3.238" style="fill: rgb(216, 216, 216);"/>
|
||||
<rect x="11.607" y="14.833" width="19.787" height="18.697" style="fill: rgb(0, 0, 0);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216);" cx="21.5" cy="24.181" rx="7" ry="7"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
let [x, y] = xy;
|
||||
let l = x + NP_PART_XOFF;
|
||||
let t = y + NP_PART_YOFF;
|
||||
let w = NP_PART_WIDTH;
|
||||
let h = NP_PART_HEIGHT;
|
||||
let img = <SVGImageElement>svg.elt("image");
|
||||
svg.hydrate(img, {class: "sim-neopixel-strip", x: l, y: t, width: w, height: h,
|
||||
href: `/parts/${NEOPIXEL_PART_IMG}`});
|
||||
return {el: img, x: l, y: t, w: w, h: h};
|
||||
svg.hydrate(img, {
|
||||
class: "sim-neopixel-strip", x: l, y: t, width: w, height: h,
|
||||
href: svg.toDataUri(NEOPIXEL_PART_IMG)
|
||||
});
|
||||
return { el: img, x: l, y: t, w: w, h: h };
|
||||
}
|
||||
export class NeoPixel implements SVGAndSize<SVGCircleElement> {
|
||||
public el: SVGCircleElement;
|
||||
public w: number;
|
||||
public h: number;
|
||||
public x: number;
|
||||
public y: number;
|
||||
public cx: number;
|
||||
export class NeoPixel {
|
||||
public el: SVGElement;
|
||||
public cy: number;
|
||||
|
||||
constructor(xy: Coord = [0, 0]) {
|
||||
let circle = <SVGCircleElement>svg.elt("circle");
|
||||
let el = <SVGElement>svg.elt("rect");
|
||||
let r = PIXEL_RADIUS;
|
||||
let [cx, cy] = xy;
|
||||
svg.hydrate(circle, {cx: cx, cy: cy, r: r, class: "sim-neopixel"});
|
||||
this.el = circle;
|
||||
this.w = r * 2;
|
||||
this.h = r * 2;
|
||||
this.x = cx - r;
|
||||
this.y = cy - r;
|
||||
this.cx = cx;
|
||||
let y = cy - r;
|
||||
svg.hydrate(el, { x: "-50%", y: y, width: "100%", height: r * 2, class: "sim-neopixel" });
|
||||
this.el = el;
|
||||
this.cy = cy;
|
||||
}
|
||||
|
||||
@ -121,14 +142,14 @@ namespace pxsim.visuals {
|
||||
"height": `${CANVAS_HEIGHT}px`,
|
||||
});
|
||||
this.canvas = el;
|
||||
this.background = <SVGRectElement>svg.child(el, "rect", { class: "sim-neopixel-background hidden"});
|
||||
this.background = <SVGRectElement>svg.child(el, "rect", { class: "sim-neopixel-background hidden" });
|
||||
this.updateViewBox(-CANVAS_VIEW_WIDTH / 2, 0, CANVAS_VIEW_WIDTH, CANVAS_VIEW_HEIGHT);
|
||||
}
|
||||
|
||||
private updateViewBox(x: number, y: number, w: number, h: number) {
|
||||
this.viewBox = [x, y, w, h];
|
||||
svg.hydrate(this.canvas, {"viewBox": `${x} ${y} ${w} ${h}`});
|
||||
svg.hydrate(this.background, {"x": x, "y": y, "width": w, "height": h});
|
||||
svg.hydrate(this.canvas, { "viewBox": `${x} ${y} ${w} ${h}` });
|
||||
svg.hydrate(this.background, { "x": x, "y": y, "width": w, "height": h });
|
||||
}
|
||||
|
||||
public update(colors: RGBW[]) {
|
||||
@ -140,11 +161,11 @@ namespace pxsim.visuals {
|
||||
if (!pixel) {
|
||||
let cxy: Coord = [0, CANVAS_VIEW_PADDING + i * PIXEL_SPACING];
|
||||
pixel = this.pixels[i] = new NeoPixel(cxy);
|
||||
svg.hydrate(pixel.el, { title: `offset: ${i}` });
|
||||
this.canvas.appendChild(pixel.el);
|
||||
}
|
||||
let color = colors[i];
|
||||
pixel.setRgb(color);
|
||||
svg.hydrate(pixel.el, {title: `offset: ${i}`});
|
||||
}
|
||||
|
||||
//show the canvas if it's hidden
|
||||
@ -164,13 +185,19 @@ namespace pxsim.visuals {
|
||||
|
||||
public setLoc(xy: Coord) {
|
||||
let [x, y] = xy;
|
||||
svg.hydrate(this.canvas, {x: x, y: y});
|
||||
svg.hydrate(this.canvas, { x: x, y: y });
|
||||
}
|
||||
};
|
||||
|
||||
function gpioPinToPinNumber(gpioPin: string): number {
|
||||
let pinNumStr = gpioPin.split("P")[1];
|
||||
let pinNum = Number(pinNumStr) + 7 /*MICROBIT_ID_IO_P0; TODO: don't hardcode this, import enums.d.ts*/;
|
||||
function digitalPinToPinNumber(gpioPin: string): number {
|
||||
const MICROBIT_ID_IO_P0 = 7; //TODO: don't hardcode this, import enums.d.ts
|
||||
if (gpioPin == "*") {
|
||||
return MICROBIT_ID_IO_P0;
|
||||
}
|
||||
let pinSplit = gpioPin.split("DigitalPin.P");
|
||||
U.assert(pinSplit.length === 2, "Unknown format for pin (for NeoPixel): " + gpioPin);
|
||||
let pinNumStr = pinSplit[1];
|
||||
let pinNum = Number(pinNumStr) + MICROBIT_ID_IO_P0;
|
||||
return pinNum
|
||||
}
|
||||
function parseNeoPixelMode(modeStr: string): NeoPixelMode {
|
||||
@ -190,7 +217,7 @@ namespace pxsim.visuals {
|
||||
return mode;
|
||||
}
|
||||
|
||||
export class NeoPixelView implements IBoardComponent<NeoPixelState> {
|
||||
export class NeoPixelView implements IBoardPart<NeoPixelState> {
|
||||
public style: string = `
|
||||
.sim-neopixel-canvas {
|
||||
}
|
||||
@ -208,6 +235,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
`;
|
||||
public element: SVGElement;
|
||||
public overElement: SVGElement;
|
||||
public defs: SVGElement[];
|
||||
private state: NeoPixelState;
|
||||
private canvas: NeoPixelCanvas;
|
||||
@ -217,22 +245,24 @@ namespace pxsim.visuals {
|
||||
private pin: number;
|
||||
private mode: NeoPixelMode;
|
||||
|
||||
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void {
|
||||
U.assert(otherArgs.length === 1, "NeoPixels assumes a RGB vs RGBW mode is passed to it");
|
||||
let modeStr = otherArgs[0];
|
||||
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, otherParams: Map<string>): void {
|
||||
U.assert(!!otherParams["mode"], "NeoPixels assumes a RGB vs RGBW mode is passed to it");
|
||||
U.assert(!!otherParams["pin"], "NeoPixels assumes a pin is passed to it");
|
||||
let modeStr = otherParams["mode"];
|
||||
this.mode = parseNeoPixelMode(modeStr);
|
||||
this.state = state;
|
||||
this.stripGroup = <SVGGElement>svg.elt("g");
|
||||
this.element = this.stripGroup;
|
||||
let pinStr = gpioPins[0];
|
||||
this.pin = gpioPinToPinNumber(pinStr);
|
||||
let pinStr = otherParams["pin"];
|
||||
this.pin = digitalPinToPinNumber(pinStr);
|
||||
this.lastLocation = [0, 0];
|
||||
let part = mkNeoPixelPart();
|
||||
this.part = part;
|
||||
this.stripGroup.appendChild(part.el);
|
||||
let canvas = new NeoPixelCanvas(this.pin);
|
||||
this.canvas = canvas;
|
||||
let canvasG = svg.child(this.stripGroup, "g", {class: "sim-neopixel-canvas-parent"});
|
||||
let canvasG = svg.elt("g", { class: "sim-neopixel-canvas-parent" });
|
||||
this.overElement = canvasG;
|
||||
canvasG.appendChild(canvas.canvas);
|
||||
this.updateStripLoc();
|
||||
}
|
||||
@ -244,13 +274,14 @@ namespace pxsim.visuals {
|
||||
}
|
||||
private updateStripLoc() {
|
||||
let [x, y] = this.lastLocation;
|
||||
U.assert(typeof x === "number" && typeof y === "number", "invalid x,y for NeoPixel strip");
|
||||
this.canvas.setLoc([x + CANVAS_LEFT, y + CANVAS_TOP]);
|
||||
svg.hydrate(this.part.el, {transform: `translate(${x} ${y})`}); //TODO: update part's l,h, etc.
|
||||
svg.hydrate(this.part.el, { transform: `translate(${x} ${y})` }); //TODO: update part's l,h, etc.
|
||||
}
|
||||
public updateState(): void {
|
||||
let colors = this.state.getColors(this.pin, this.mode);
|
||||
this.canvas.update(colors);
|
||||
}
|
||||
public updateTheme (): void { }
|
||||
public updateTheme(): void { }
|
||||
}
|
||||
}
|
@ -1,425 +0,0 @@
|
||||
namespace pxsim.visuals {
|
||||
const WIRE_WIDTH = PIN_DIST / 2.5;
|
||||
const BB_WIRE_SMOOTH = 0.7;
|
||||
const INSTR_WIRE_SMOOTH = 0.8;
|
||||
const WIRE_PART_CURVE_OFF = 15;
|
||||
const WIRE_PART_LENGTH = 100;
|
||||
export const WIRES_CSS = `
|
||||
.sim-bb-wire {
|
||||
fill:none;
|
||||
stroke-linecap: round;
|
||||
stroke-width:${WIRE_WIDTH}px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.sim-bb-wire-end {
|
||||
stroke:#333;
|
||||
fill:#333;
|
||||
}
|
||||
.sim-bb-wire-bare-end {
|
||||
fill: #ccc;
|
||||
}
|
||||
.sim-bb-wire-hover {
|
||||
stroke-width: ${WIRE_WIDTH}px;
|
||||
visibility: hidden;
|
||||
stroke-dasharray: ${PIN_DIST / 10.0},${PIN_DIST / 1.5};
|
||||
/*stroke-opacity: 0.4;*/
|
||||
}
|
||||
.grayed .sim-bb-wire-ends-g:not(.highlight) .sim-bb-wire-end {
|
||||
stroke: #777;
|
||||
fill: #777;
|
||||
}
|
||||
.grayed .sim-bb-wire:not(.highlight) {
|
||||
stroke: #CCC;
|
||||
}
|
||||
.sim-bb-wire-ends-g:hover .sim-bb-wire-end {
|
||||
stroke: red;
|
||||
fill: red;
|
||||
}
|
||||
.sim-bb-wire-ends-g:hover .sim-bb-wire-bare-end {
|
||||
stroke: #FFF;
|
||||
fill: #FFF;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface Wire {
|
||||
endG: SVGGElement;
|
||||
end1: SVGElement;
|
||||
end2: SVGElement;
|
||||
wires: SVGElement[];
|
||||
}
|
||||
|
||||
function cssEncodeColor(color: string): string {
|
||||
//HACK/TODO: do real CSS encoding.
|
||||
return color
|
||||
.replace(/\#/g, "-")
|
||||
.replace(/\(/g, "-")
|
||||
.replace(/\)/g, "-")
|
||||
.replace(/\,/g, "-")
|
||||
.replace(/\./g, "-")
|
||||
.replace(/\s/g, "");
|
||||
}
|
||||
export enum WireEndStyle {
|
||||
BBJumper,
|
||||
OpenJumper,
|
||||
Croc,
|
||||
}
|
||||
export interface WireOpts { //TODO: use throughout
|
||||
color?: string,
|
||||
colorClass?: string,
|
||||
bendFactor?: number,
|
||||
}
|
||||
export function mkWirePart(cp: [number, number], clr: string, croc: boolean = false): visuals.SVGAndSize<SVGGElement> {
|
||||
let g = <SVGGElement>svg.elt("g");
|
||||
let [cx, cy] = cp;
|
||||
let offset = WIRE_PART_CURVE_OFF;
|
||||
let p1: visuals.Coord = [cx - offset, cy - WIRE_PART_LENGTH / 2];
|
||||
let p2: visuals.Coord = [cx + offset, cy + WIRE_PART_LENGTH / 2];
|
||||
clr = visuals.mapWireColor(clr);
|
||||
let e1: SVGElAndSize;
|
||||
if (croc)
|
||||
e1 = mkCrocEnd(p1, true, clr);
|
||||
else
|
||||
e1 = mkOpenJumperEnd(p1, true, clr);
|
||||
let s = mkWirePartSeg(p1, p2, clr);
|
||||
let e2 = mkOpenJumperEnd(p2, false, clr);
|
||||
g.appendChild(s.el);
|
||||
g.appendChild(e1.el);
|
||||
g.appendChild(e2.el);
|
||||
let l = Math.min(e1.x, e2.x);
|
||||
let r = Math.max(e1.x + e1.w, e2.x + e2.w);
|
||||
let t = Math.min(e1.y, e2.y);
|
||||
let b = Math.max(e1.y + e1.h, e2.y + e2.h);
|
||||
return {el: g, x: l, y: t, w: r - l, h: b - t};
|
||||
}
|
||||
function mkCurvedWireSeg(p1: [number, number], p2: [number, number], smooth: number, clrClass: string): SVGPathElement {
|
||||
const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
|
||||
let [x1, y1] = p1;
|
||||
let [x2, y2] = p2
|
||||
let yLen = (y2 - y1);
|
||||
let c1: [number, number] = [x1, y1 + yLen * smooth];
|
||||
let c2: [number, number] = [x2, y2 - yLen * smooth];
|
||||
let w = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`);
|
||||
svg.addClass(w, `wire-stroke-${clrClass}`);
|
||||
return w;
|
||||
}
|
||||
function mkWirePartSeg(p1: [number, number], p2: [number, number], clr: string): visuals.SVGAndSize<SVGPathElement> {
|
||||
//TODO: merge with mkCurvedWireSeg
|
||||
const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
|
||||
let [x1, y1] = p1;
|
||||
let [x2, y2] = p2
|
||||
let yLen = (y2 - y1);
|
||||
let c1: [number, number] = [x1, y1 + yLen * .8];
|
||||
let c2: [number, number] = [x2, y2 - yLen * .8];
|
||||
let e = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`);
|
||||
(<any>e).style["stroke"] = clr;
|
||||
return {el: e, x: Math.min(x1, x2), y: Math.min(y1, y2), w: Math.abs(x1 - x2), h: Math.abs(y1 - y2)};
|
||||
}
|
||||
function mkWireSeg(p1: [number, number], p2: [number, number], clrClass: string): SVGPathElement {
|
||||
const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
|
||||
let w = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} L${coordStr(p2)}`);
|
||||
svg.addClass(w, `wire-stroke-${clrClass}`);
|
||||
return w;
|
||||
}
|
||||
function mkBBJumperEnd(p: [number, number], clrClass: string): SVGElement {
|
||||
const endW = PIN_DIST / 4;
|
||||
let w = svg.elt("circle");
|
||||
let x = p[0];
|
||||
let y = p[1];
|
||||
let r = WIRE_WIDTH / 2 + endW / 2;
|
||||
svg.hydrate(w, {cx: x, cy: y, r: r, class: "sim-bb-wire-end"});
|
||||
svg.addClass(w, `wire-fill-${clrClass}`);
|
||||
(<any>w).style["stroke-width"] = `${endW}px`;
|
||||
return w;
|
||||
}
|
||||
function mkOpenJumperEnd(p: [number, number], top: boolean, clr: string): visuals.SVGElAndSize {
|
||||
let k = visuals.PIN_DIST * 0.24;
|
||||
let plasticLength = k * 10;
|
||||
let plasticWidth = k * 2;
|
||||
let metalLength = k * 6;
|
||||
let metalWidth = k;
|
||||
const strokeWidth = visuals.PIN_DIST / 4.0;
|
||||
let [cx, cy] = p;
|
||||
let o = top ? -1 : 1;
|
||||
let g = svg.elt("g")
|
||||
|
||||
let el = svg.elt("rect");
|
||||
let h1 = plasticLength;
|
||||
let w1 = plasticWidth;
|
||||
let x1 = cx - w1 / 2;
|
||||
let y1 = cy - (h1 / 2);
|
||||
svg.hydrate(el, {x: x1, y: y1, width: w1, height: h1, rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
|
||||
(<any>el).style["stroke-width"] = `${strokeWidth}px`;
|
||||
|
||||
let el2 = svg.elt("rect");
|
||||
let h2 = metalLength;
|
||||
let w2 = metalWidth;
|
||||
let cy2 = cy + o * (h1 / 2 + h2 / 2);
|
||||
let x2 = cx - w2 / 2;
|
||||
let y2 = cy2 - (h2 / 2);
|
||||
svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
|
||||
(<any>el2).style["fill"] = `#bbb`;
|
||||
|
||||
g.appendChild(el2);
|
||||
g.appendChild(el);
|
||||
return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
|
||||
}
|
||||
function mkCrocEnd(p: [number, number], top: boolean, clr: string): SVGElAndSize {
|
||||
//TODO: merge with mkOpenJumperEnd()
|
||||
let k = visuals.PIN_DIST * 0.24;
|
||||
const plasticWidth = k * 4;
|
||||
const plasticLength = k * 10.0;
|
||||
const metalWidth = k * 3.5;
|
||||
const metalHeight = k * 3.5;
|
||||
const pointScalar = .15;
|
||||
const baseScalar = .3;
|
||||
const taperScalar = .7;
|
||||
const strokeWidth = visuals.PIN_DIST / 4.0;
|
||||
let [cx, cy] = p;
|
||||
let o = top ? -1 : 1;
|
||||
let g = svg.elt("g")
|
||||
|
||||
let el = svg.elt("polygon");
|
||||
let h1 = plasticLength;
|
||||
let w1 = plasticWidth;
|
||||
let x1 = cx - w1 / 2;
|
||||
let y1 = cy - (h1 / 2);
|
||||
let mkPnt = (xy: Coord) => `${xy[0]},${xy[1]}`;
|
||||
let mkPnts = (...xys: Coord[]) => xys.map(xy => mkPnt(xy)).join(" ");
|
||||
const topScalar = top ? pointScalar : baseScalar;
|
||||
const midScalar = top ? taperScalar : (1 - taperScalar);
|
||||
const botScalar = top ? baseScalar : pointScalar;
|
||||
svg.hydrate(el, {
|
||||
points: mkPnts(
|
||||
[x1 + w1 * topScalar, y1], //TL
|
||||
[x1 + w1 * (1 - topScalar), y1], //TR
|
||||
[x1 + w1, y1 + h1 * midScalar], //MR
|
||||
[x1 + w1 * (1 - botScalar), y1 + h1], //BR
|
||||
[x1 + w1 * botScalar, y1 + h1], //BL
|
||||
[x1, y1 + h1 * midScalar]) //ML
|
||||
});
|
||||
svg.hydrate(el, {rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
|
||||
(<any>el).style["stroke-width"] = `${strokeWidth}px`;
|
||||
|
||||
let el2 = svg.elt("rect");
|
||||
let h2 = metalWidth;
|
||||
let w2 = metalHeight;
|
||||
let cy2 = cy + o * (h1 / 2 + h2 / 2);
|
||||
let x2 = cx - w2 / 2;
|
||||
let y2 = cy2 - (h2 / 2);
|
||||
svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
|
||||
|
||||
g.appendChild(el2);
|
||||
g.appendChild(el);
|
||||
return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
|
||||
}
|
||||
|
||||
//TODO: make this stupid class obsolete
|
||||
export class WireFactory {
|
||||
private underboard: SVGGElement;
|
||||
private overboard: SVGGElement;
|
||||
private boardEdges: number[];
|
||||
private getLocCoord: (loc: Loc) => Coord;
|
||||
public styleEl: SVGStyleElement;
|
||||
|
||||
constructor(underboard: SVGGElement, overboard: SVGGElement, boardEdges: number[], styleEl: SVGStyleElement, getLocCoord: (loc: Loc) => Coord) {
|
||||
this.styleEl = styleEl;
|
||||
this.styleEl.textContent += WIRES_CSS;
|
||||
this.underboard = underboard;
|
||||
this.overboard = overboard;
|
||||
this.boardEdges = boardEdges;
|
||||
this.getLocCoord = getLocCoord;
|
||||
}
|
||||
|
||||
private indexOfMin(vs: number[]): number {
|
||||
let minIdx = 0;
|
||||
let min = vs[0];
|
||||
for (let i = 1; i < vs.length; i++) {
|
||||
if (vs[i] < min) {
|
||||
min = vs[i];
|
||||
minIdx = i;
|
||||
}
|
||||
}
|
||||
return minIdx;
|
||||
}
|
||||
private closestEdgeIdx(p: [number, number]): number {
|
||||
let dists = this.boardEdges.map(e => Math.abs(p[1] - e));
|
||||
let edgeIdx = this.indexOfMin(dists);
|
||||
return edgeIdx;
|
||||
}
|
||||
private closestEdge(p: [number, number]): number {
|
||||
return this.boardEdges[this.closestEdgeIdx(p)];
|
||||
}
|
||||
|
||||
private nextWireId = 0;
|
||||
private drawWire(pin1: Coord, pin2: Coord, color: string): Wire {
|
||||
let wires: SVGElement[] = [];
|
||||
let g = svg.child(this.overboard, "g", {class: "sim-bb-wire-group"});
|
||||
const closestPointOffBoard = (p: [number, number]): [number, number] => {
|
||||
const offset = PIN_DIST / 2;
|
||||
let e = this.closestEdge(p);
|
||||
let y: number;
|
||||
if (e - p[1] < 0)
|
||||
y = e - offset;
|
||||
else
|
||||
y = e + offset;
|
||||
return [p[0], y];
|
||||
}
|
||||
let wireId = this.nextWireId++;
|
||||
let clrClass = cssEncodeColor(color);
|
||||
let end1 = mkBBJumperEnd(pin1, clrClass);
|
||||
let end2 = mkBBJumperEnd(pin2, clrClass);
|
||||
let endG = <SVGGElement>svg.child(g, "g", {class: "sim-bb-wire-ends-g"});
|
||||
endG.appendChild(end1);
|
||||
endG.appendChild(end2);
|
||||
let edgeIdx1 = this.closestEdgeIdx(pin1);
|
||||
let edgeIdx2 = this.closestEdgeIdx(pin2);
|
||||
if (edgeIdx1 == edgeIdx2) {
|
||||
let seg = mkWireSeg(pin1, pin2, clrClass);
|
||||
g.appendChild(seg);
|
||||
wires.push(seg);
|
||||
} else {
|
||||
let offP1 = closestPointOffBoard(pin1);
|
||||
let offP2 = closestPointOffBoard(pin2);
|
||||
let offSeg1 = mkWireSeg(pin1, offP1, clrClass);
|
||||
let offSeg2 = mkWireSeg(pin2, offP2, clrClass);
|
||||
let midSeg: SVGElement;
|
||||
let midSegHover: SVGElement;
|
||||
let isBetweenMiddleTwoEdges = (edgeIdx1 == 1 || edgeIdx1 == 2) && (edgeIdx2 == 1 || edgeIdx2 == 2);
|
||||
if (isBetweenMiddleTwoEdges) {
|
||||
midSeg = mkCurvedWireSeg(offP1, offP2, BB_WIRE_SMOOTH, clrClass);
|
||||
midSegHover = mkCurvedWireSeg(offP1, offP2, BB_WIRE_SMOOTH, clrClass);
|
||||
} else {
|
||||
midSeg = mkWireSeg(offP1, offP2, clrClass);
|
||||
midSegHover = mkWireSeg(offP1, offP2, clrClass);
|
||||
}
|
||||
svg.addClass(midSegHover, "sim-bb-wire-hover");
|
||||
g.appendChild(offSeg1);
|
||||
wires.push(offSeg1);
|
||||
g.appendChild(offSeg2);
|
||||
wires.push(offSeg2);
|
||||
this.underboard.appendChild(midSeg);
|
||||
wires.push(midSeg);
|
||||
g.appendChild(midSegHover);
|
||||
wires.push(midSegHover);
|
||||
//set hover mechanism
|
||||
let wireIdClass = `sim-bb-wire-id-${wireId}`;
|
||||
const setId = (e: SVGElement) => svg.addClass(e, wireIdClass);
|
||||
setId(endG);
|
||||
setId(midSegHover);
|
||||
this.styleEl.textContent += `
|
||||
.${wireIdClass}:hover ~ .${wireIdClass}.sim-bb-wire-hover {
|
||||
visibility: visible;
|
||||
}`
|
||||
}
|
||||
|
||||
// wire colors
|
||||
let colorCSS = `
|
||||
.wire-stroke-${clrClass} {
|
||||
stroke: ${mapWireColor(color)};
|
||||
}
|
||||
.wire-fill-${clrClass} {
|
||||
fill: ${mapWireColor(color)};
|
||||
}
|
||||
`
|
||||
this.styleEl.textContent += colorCSS;
|
||||
|
||||
return {endG: endG, end1: end1, end2: end2, wires: wires};
|
||||
}
|
||||
private drawWireWithCrocs(pin1: Coord, pin2: Coord, color: string): Wire {
|
||||
//TODO: merge with drawWire()
|
||||
const PIN_Y_OFF = 40;
|
||||
const CROC_Y_OFF = -17;
|
||||
let wires: SVGElement[] = [];
|
||||
let g = svg.child(this.overboard, "g", {class: "sim-bb-wire-group"});
|
||||
const closestPointOffBoard = (p: [number, number]): [number, number] => {
|
||||
const offset = PIN_DIST / 2;
|
||||
let e = this.closestEdge(p);
|
||||
let y: number;
|
||||
if (e - p[1] < 0)
|
||||
y = e - offset;
|
||||
else
|
||||
y = e + offset;
|
||||
return [p[0], y];
|
||||
}
|
||||
let wireId = this.nextWireId++;
|
||||
let clrClass = cssEncodeColor(color);
|
||||
let end1 = mkBBJumperEnd(pin1, clrClass);
|
||||
let pin2orig = pin2;
|
||||
let [x2, y2] = pin2;
|
||||
pin2 = [x2, y2 + PIN_Y_OFF];//HACK
|
||||
[x2, y2] = pin2;
|
||||
let endCoord2: Coord = [x2, y2 + CROC_Y_OFF]
|
||||
let end2AndSize = mkCrocEnd(endCoord2, true, color);
|
||||
let end2 = end2AndSize.el;
|
||||
let endG = <SVGGElement>svg.child(g, "g", {class: "sim-bb-wire-ends-g"});
|
||||
endG.appendChild(end1);
|
||||
//endG.appendChild(end2);
|
||||
let edgeIdx1 = this.closestEdgeIdx(pin1);
|
||||
let edgeIdx2 = this.closestEdgeIdx(pin2orig);
|
||||
if (edgeIdx1 == edgeIdx2) {
|
||||
let seg = mkWireSeg(pin1, pin2, clrClass);
|
||||
g.appendChild(seg);
|
||||
wires.push(seg);
|
||||
} else {
|
||||
let offP1 = closestPointOffBoard(pin1);
|
||||
//let offP2 = closestPointOffBoard(pin2orig);
|
||||
let offSeg1 = mkWireSeg(pin1, offP1, clrClass);
|
||||
//let offSeg2 = mkWireSeg(pin2, offP2, clrClass);
|
||||
let midSeg: SVGElement;
|
||||
let midSegHover: SVGElement;
|
||||
let isBetweenMiddleTwoEdges = (edgeIdx1 == 1 || edgeIdx1 == 2) && (edgeIdx2 == 1 || edgeIdx2 == 2);
|
||||
if (isBetweenMiddleTwoEdges) {
|
||||
midSeg = mkCurvedWireSeg(offP1, pin2, BB_WIRE_SMOOTH, clrClass);
|
||||
midSegHover = mkCurvedWireSeg(offP1, pin2, BB_WIRE_SMOOTH, clrClass);
|
||||
} else {
|
||||
midSeg = mkWireSeg(offP1, pin2, clrClass);
|
||||
midSegHover = mkWireSeg(offP1, pin2, clrClass);
|
||||
}
|
||||
svg.addClass(midSegHover, "sim-bb-wire-hover");
|
||||
g.appendChild(offSeg1);
|
||||
wires.push(offSeg1);
|
||||
// g.appendChild(offSeg2);
|
||||
// wires.push(offSeg2);
|
||||
this.underboard.appendChild(midSeg);
|
||||
wires.push(midSeg);
|
||||
//g.appendChild(midSegHover);
|
||||
//wires.push(midSegHover);
|
||||
//set hover mechanism
|
||||
let wireIdClass = `sim-bb-wire-id-${wireId}`;
|
||||
const setId = (e: SVGElement) => svg.addClass(e, wireIdClass);
|
||||
setId(endG);
|
||||
setId(midSegHover);
|
||||
this.styleEl.textContent += `
|
||||
.${wireIdClass}:hover ~ .${wireIdClass}.sim-bb-wire-hover {
|
||||
visibility: visible;
|
||||
}`
|
||||
}
|
||||
endG.appendChild(end2);//HACK
|
||||
|
||||
// wire colors
|
||||
let colorCSS = `
|
||||
.wire-stroke-${clrClass} {
|
||||
stroke: ${mapWireColor(color)};
|
||||
}
|
||||
.wire-fill-${clrClass} {
|
||||
fill: ${mapWireColor(color)};
|
||||
}
|
||||
`
|
||||
this.styleEl.textContent += colorCSS;
|
||||
|
||||
return {endG: endG, end1: end1, end2: end2, wires: wires};
|
||||
}
|
||||
|
||||
public addWire(start: Loc, end: Loc, color: string, withCrocs: boolean = false): Wire {
|
||||
let startLoc = this.getLocCoord(start);
|
||||
let endLoc = this.getLocCoord(end);
|
||||
let wireEls: Wire;
|
||||
if (withCrocs && end.type == "dalboard") {
|
||||
wireEls = this.drawWireWithCrocs(startLoc, endLoc, color);
|
||||
} else {
|
||||
wireEls = this.drawWire(startLoc, endLoc, color);
|
||||
}
|
||||
return wireEls;
|
||||
}
|
||||
}
|
||||
}
|