Compare commits

...

82 Commits

Author SHA1 Message Date
46f7831e7c 0.4.7 2016-09-14 22:21:00 -07:00
dda29a5cb6 Bump pxt-core to 0.4.11 2016-09-14 22:20:58 -07:00
6e4f4595a2 updated usb images 2016-09-14 22:18:01 -07:00
cdbe1e513b 0.4.6 2016-09-14 20:38:49 -07:00
ae15c9a656 Bump pxt-core to 0.4.9 2016-09-14 20:38:47 -07:00
d993ff3a9d 0.4.5 2016-09-14 11:33:54 -07:00
13785a2438 OS X uploader (#252)
* Source for OS X uploader

* Readme for OS X uploader

* Export image

* .gitignore for Xcode project

* Remove redundant data

* Update readme instructions

* List formatting

* Remove personal copyright notice added by Xcode

* Added release build and updated readme

* point to doc cdn
2016-09-14 11:33:11 -07:00
4dfb77fcd7 0.4.4 2016-09-14 08:16:32 -07:00
70deffb665 Bump pxt-core to 0.4.8 2016-09-14 08:16:29 -07:00
41a148de28 Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-14 08:14:41 -07:00
886e071d7f updated about to link to docs 2016-09-14 08:14:04 -07:00
8a58d664c3 adding clear image in stopanimation 2016-09-14 07:54:35 -07:00
71d1155f21 Add file forgotten in 50473255a8 2016-09-14 12:31:52 +03:00
167c1d8fce loading board definition from pxtarget.json 2016-09-13 15:32:12 -07:00
e59ae37954 moving neopixel state to pxt 2016-09-13 13:09:02 -07:00
72a621ec8b mvoing edge connector to pxt 2016-09-13 12:48:07 -07:00
d6ff930333 fix capitalized file names 2016-09-13 10:36:25 -07:00
54a7ac81ea Recommended browser documentation (#251)
* Documentation page for unsupported browsers

* Update unsupported documentation with mac info

* Blur irrelevant information in version screenshots

* Rename to Peli's suggested path

* Browser recommendation for each platform
2016-09-13 10:33:06 -07:00
e5d985dbf1 moving boardhost to pxt 2016-09-13 09:59:34 -07:00
9db91d89d6 refactor part global lists 2016-09-13 09:44:58 -07:00
1fa9bf12d5 refactoring dalboard 2016-09-12 21:29:55 -07:00
61bab257eb 0.4.3 2016-09-12 11:45:22 -07:00
2e90b351da updated other download picture 2016-09-12 11:33:16 -07:00
801bd6c7a0 udpated download pictures 2016-09-12 11:30:12 -07:00
777ba40899 0.4.2 2016-09-12 11:19:41 -07:00
0d11c16ecf Bump pxt-core to 0.4.3 2016-09-12 11:19:39 -07:00
9c1628b977 Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-12 11:11:12 -07:00
953b362b34 Download instructions (#250)
* Images of micro:bit attached t ostuff

* Cropped and enhanced USB images

* Define paths for USB images

* Added new matching criteria for images

* Help URL

* Add paths for all images

* Cropped images so they look better on dialog

* Add link to uploader
2016-09-12 11:10:30 -07:00
8b40850a94 tweaks to neopixel rendering 2016-09-11 21:55:54 -07:00
fc3a02cc41 0.4.1 2016-09-11 18:07:11 +01:00
80f2ff6757 Bump to 0.4 to match pxt-core 2016-09-11 18:07:05 +01:00
6f790d167c 0.3.89 2016-09-11 18:06:20 +01:00
ac7502074a Bump pxt-core to 0.4.2 2016-09-11 18:06:20 +01:00
50473255a8 Utylize the new target.css file 2016-09-11 17:51:41 +01:00
910772d54e refacotring various simulator features into pxt 2016-09-09 22:56:26 -07:00
33f12f9ecc 0.3.88 2016-09-09 16:41:31 -07:00
01fa4ef53a Bump pxt-core to 0.3.102 2016-09-09 16:41:14 -07:00
3858b0a0a0 moving wiring to pxt 2016-09-09 15:01:29 -07:00
da30afb121 Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-09 13:43:45 -07:00
086bcf372f remove dependencies on dal.d.ts 2016-09-09 13:43:41 -07:00
236e7337e2 0.3.87 2016-09-09 20:49:21 +01:00
4cf223271f Bump pxt-core to 0.3.100 2016-09-09 20:49:21 +01:00
10180f4729 Merge branch 'master' of github.com:Microsoft/pxt-microbit 2016-09-09 20:49:19 +01:00
618dd33221 No longer used 2016-09-09 20:33:11 +01:00
7356e5e52e New index.html expansion 2016-09-09 20:26:18 +01:00
a59d148eb5 0.3.86 2016-09-09 12:23:25 -07:00
34ce687bbd Bump pxt-core to 0.3.98 2016-09-09 12:23:24 -07:00
5e241ed566 Use new doc templates 2016-09-09 20:19:52 +01:00
dcb2b21e66 removing dal dependency 2016-09-09 11:36:36 -07:00
95669e174a Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-09 09:59:21 -07:00
aca7d6113d enable streams in pxtarget 2016-09-09 09:59:07 -07:00
ef4b06a087 Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-09 15:19:58 +01:00
afa69c23c4 0.3.85 2016-09-09 11:35:45 +01:00
8229e71d0a Bump pxt-core to 0.3.97 2016-09-09 11:35:45 +01:00
93770e5821 Merge branch 'master' of github.com:Microsoft/pxt-microbit 2016-09-09 10:17:35 +01:00
cb8e28beb0 Bump pxt-microbit-core to 0.5.0 2016-09-09 10:17:28 +01:00
8c7e6055ff enabling parts in target 2016-09-09 01:39:52 -07:00
8450db55ac 0.3.84 2016-09-09 01:25:28 -07:00
c63e2c85f1 new part definitions (#247)
* working on new part definitions

* draft of new part definitions

* updates comments

* starting new allocator

* starting from the old allocator

* alloc internals renaming

* alloc minor renaming

* alloc internal renaming

* progress on new parts definition

* progress on new part defs allocator

* refactors BBLoc; progress on new allocator

* more progress on new allocator

* finishing new allocator

* deleting old allocator

* moves new allocator and part definitions

* porting to new part definitions

* refactors instructions for new definitions

* debugging new allocator

* fixes ground and power wire colros

* fixing new part definition bugs

* fixes wire end offsets; fixes NeoPixel placement

* fixes colorGroup issue

* fixes led matrix wiring

* naming tweaks

* fixes instructions regressions

* typo
2016-09-09 01:23:39 -07:00
d8fc11a688 0.3.83 2016-09-09 01:20:45 -07:00
847a29848d Bump pxt-core to 0.3.96 2016-09-09 01:20:43 -07:00
39f4372ee5 0.3.82 2016-09-08 23:39:26 -07:00
a8bbbea8ed Bump pxt-core to 0.3.95 2016-09-08 23:39:24 -07:00
91a8d05e45 updated loc strings 2016-09-08 23:11:58 -07:00
3ecedd3a32 0.3.81 2016-09-08 01:02:46 -07:00
b6390d77dd Bump pxt-core to 0.3.94 2016-09-08 01:02:40 -07:00
e8f9e0f023 lighter rendering of breadboard in grayed 2016-09-08 00:54:19 -07:00
2bacb58fdd simpler instructions 2016-09-08 00:46:43 -07:00
af8babfea2 Merge branch 'master' of https://github.com/Microsoft/pxt-microbit 2016-09-08 00:03:33 -07:00
1ee6274100 added raspberry-pi instructions 2016-09-08 00:00:07 -07:00
7448db5b98 makes instruction panels inline-block 2016-09-07 15:00:42 -07:00
983645403b 0.3.80 2016-09-07 11:49:41 -07:00
7a9f382bee Merge pull request #240 from Microsoft/neopixel-improvement
improves neopixel simulator
2016-09-07 11:22:40 -07:00
e59fd8469b improves neopixel simulator 2016-09-07 10:58:44 -07:00
008c886de9 0.3.79 2016-09-07 16:51:08 +01:00
c87fa30738 Bump pxt-core to 0.3.93 2016-09-07 16:51:08 +01:00
dd9d1299fa Merge branch 'master' of github.com:Microsoft/pxt-microbit 2016-09-07 16:27:42 +01:00
6a7d1bd95c Support for object literals
bump pxt-microbit-core to 0.4.4
see https://github.com/Microsoft/pxt/issues/287
2016-09-07 16:27:28 +01:00
dfe270d259 Correct filename of data.csv in radio challenge 2016-09-07 11:32:41 +01:00
6deb0683b6 Add GitHub URL property 2016-09-07 10:33:36 +01:00
0a746f5bbe 0.3.78 2016-09-06 20:41:04 -07:00
066408c317 Bump pxt-core to 0.3.92 2016-09-06 20:41:02 -07:00
98 changed files with 2463 additions and 3369 deletions

137
clients/macuploader/.gitignore vendored Normal file
View File

@ -0,0 +1,137 @@
# Created by https://www.gitignore.io/api/osx,xcode,objective-c,vim
### OSX ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Xcode ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
### Objective-C ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xcuserstate
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### Objective-C Patch ###
*.xcscmblueprint
### Vim ###
# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

View File

@ -0,0 +1,307 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
E93040071D895D1F00D931CA /* DirectoryWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = E93040061D895D1F00D931CA /* DirectoryWatcher.m */; };
E930400A1D89620900D931CA /* Uploader.m in Sources */ = {isa = PBXBuildFile; fileRef = E93040091D89620900D931CA /* Uploader.m */; };
E9F4FEE21D8709980071D783 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F4FEE11D8709980071D783 /* AppDelegate.m */; };
E9F4FEE51D8709980071D783 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F4FEE41D8709980071D783 /* main.m */; };
E9F4FEE71D8709980071D783 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9F4FEE61D8709980071D783 /* Assets.xcassets */; };
E9F4FEEA1D8709980071D783 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9F4FEE81D8709980071D783 /* MainMenu.xib */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
E93040051D895D1F00D931CA /* DirectoryWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryWatcher.h; sourceTree = "<group>"; };
E93040061D895D1F00D931CA /* DirectoryWatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DirectoryWatcher.m; sourceTree = "<group>"; };
E93040081D89620900D931CA /* Uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Uploader.h; sourceTree = "<group>"; };
E93040091D89620900D931CA /* Uploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Uploader.m; sourceTree = "<group>"; };
E9F4FEDD1D8709980071D783 /* Microbit Uploader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Microbit Uploader.app"; sourceTree = BUILT_PRODUCTS_DIR; };
E9F4FEE01D8709980071D783 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
E9F4FEE11D8709980071D783 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
E9F4FEE41D8709980071D783 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
E9F4FEE61D8709980071D783 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
E9F4FEE91D8709980071D783 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
E9F4FEEB1D8709980071D783 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
E9F4FEDA1D8709980071D783 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
E9F4FED41D8709980071D783 = {
isa = PBXGroup;
children = (
E9F4FEDF1D8709980071D783 /* Microbit Uploader */,
E9F4FEDE1D8709980071D783 /* Products */,
);
sourceTree = "<group>";
};
E9F4FEDE1D8709980071D783 /* Products */ = {
isa = PBXGroup;
children = (
E9F4FEDD1D8709980071D783 /* Microbit Uploader.app */,
);
name = Products;
sourceTree = "<group>";
};
E9F4FEDF1D8709980071D783 /* Microbit Uploader */ = {
isa = PBXGroup;
children = (
E9F4FEE01D8709980071D783 /* AppDelegate.h */,
E9F4FEE11D8709980071D783 /* AppDelegate.m */,
E9F4FEE61D8709980071D783 /* Assets.xcassets */,
E9F4FEE81D8709980071D783 /* MainMenu.xib */,
E9F4FEEB1D8709980071D783 /* Info.plist */,
E9F4FEE31D8709980071D783 /* Supporting Files */,
E93040051D895D1F00D931CA /* DirectoryWatcher.h */,
E93040061D895D1F00D931CA /* DirectoryWatcher.m */,
E93040081D89620900D931CA /* Uploader.h */,
E93040091D89620900D931CA /* Uploader.m */,
);
path = "Microbit Uploader";
sourceTree = "<group>";
};
E9F4FEE31D8709980071D783 /* Supporting Files */ = {
isa = PBXGroup;
children = (
E9F4FEE41D8709980071D783 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
E9F4FEDC1D8709980071D783 /* Microbit Uploader */ = {
isa = PBXNativeTarget;
buildConfigurationList = E9F4FEEE1D8709980071D783 /* Build configuration list for PBXNativeTarget "Microbit Uploader" */;
buildPhases = (
E9F4FED91D8709980071D783 /* Sources */,
E9F4FEDA1D8709980071D783 /* Frameworks */,
E9F4FEDB1D8709980071D783 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Microbit Uploader";
productName = "Microbit Uploader";
productReference = E9F4FEDD1D8709980071D783 /* Microbit Uploader.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
E9F4FED51D8709980071D783 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = thomasdenney;
TargetAttributes = {
E9F4FEDC1D8709980071D783 = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = E9F4FED81D8709980071D783 /* Build configuration list for PBXProject "Microbit Uploader" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = E9F4FED41D8709980071D783;
productRefGroup = E9F4FEDE1D8709980071D783 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
E9F4FEDC1D8709980071D783 /* Microbit Uploader */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
E9F4FEDB1D8709980071D783 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E9F4FEE71D8709980071D783 /* Assets.xcassets in Resources */,
E9F4FEEA1D8709980071D783 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
E9F4FED91D8709980071D783 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E9F4FEE51D8709980071D783 /* main.m in Sources */,
E930400A1D89620900D931CA /* Uploader.m in Sources */,
E9F4FEE21D8709980071D783 /* AppDelegate.m in Sources */,
E93040071D895D1F00D931CA /* DirectoryWatcher.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
E9F4FEE81D8709980071D783 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
E9F4FEE91D8709980071D783 /* Base */,
);
name = MainMenu.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
E9F4FEEC1D8709980071D783 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
E9F4FEED1D8709980071D783 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
};
name = Release;
};
E9F4FEEF1D8709980071D783 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "Microbit Uploader/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.thomasdenney.Microbit-Uploader";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
E9F4FEF01D8709980071D783 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "Microbit Uploader/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.thomasdenney.Microbit-Uploader";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
E9F4FED81D8709980071D783 /* Build configuration list for PBXProject "Microbit Uploader" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E9F4FEEC1D8709980071D783 /* Debug */,
E9F4FEED1D8709980071D783 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E9F4FEEE1D8709980071D783 /* Build configuration list for PBXNativeTarget "Microbit Uploader" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E9F4FEEF1D8709980071D783 /* Debug */,
E9F4FEF01D8709980071D783 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = E9F4FED51D8709980071D783 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Microbit Uploader.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,6 @@
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

View File

@ -0,0 +1,124 @@
#import "AppDelegate.h"
#import "DirectoryWatcher.h"
#import "Uploader.h"
@interface AppDelegate ()<DirectoryWatcherDelegate, UploaderDelegate, NSUserNotificationCenterDelegate>
@property (weak) IBOutlet NSWindow *window;
@property DirectoryWatcher * watcher;
@property Uploader * uploader;
@property NSStatusItem * menubarItem;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
self.watcher = [[DirectoryWatcher alloc] initWithPath:[self downloadsDirectory]];
self.watcher.delegate = self;
[self.watcher startWatching];
self.uploader = [[Uploader alloc] init];
self.uploader.delegate = self;
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = self;
[self createMenuBarIcon];
[self configureVolumeMountNotifications];
[self showActiveMicroBits];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
[self.watcher stopWatching];
}
- (void)dealloc {
[[NSWorkspace sharedWorkspace].notificationCenter removeObserver:self];
}
#pragma mark - Directory
- (void)watcher:(DirectoryWatcher *)watcher observedNewFileAtPath:(NSString *)path {
NSString * fullPath = [watcher.path stringByAppendingPathComponent:path];
if ([self.uploader shouldUploadFileAtPath:fullPath]) {
[self.uploader uploadFile:fullPath];
}
}
- (NSString*)downloadsDirectory {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
return paths.firstObject;
}
#pragma mark - Uploader delegate
- (void)uploader:(Uploader *)uploader transferredFile:(NSString *)file toMicroBit:(NSString *)microbit {
[self showNotification:@"micro:bit upload" withDescription:[NSString stringWithFormat:@"%@ uploaded to %@", file.lastPathComponent, microbit]];
}
- (void)uploader:(Uploader *)uploader failedToTransferFile:(NSString *)file toMicroBit:(NSString *)microbit {
[self showNotification:@"micro:bit upload failed" withDescription:[NSString stringWithFormat:@"Couldn't transfer %@ to %@", file.lastPathComponent, microbit]];
}
- (void)showNotification:(NSString*)title withDescription:(NSString*)description {
NSUserNotification * notification = [NSUserNotification new];
notification.title = title;
notification.informativeText = description;
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
#pragma mark - NSUserNotificationCenterDelegate
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification {
return YES;
}
#pragma mark - Volume mount/unmount notification
- (void)configureVolumeMountNotifications {
[[NSWorkspace sharedWorkspace].notificationCenter addObserver:self selector:@selector(volumeMountNotification:) name:NSWorkspaceDidRenameVolumeNotification object:nil];
[[NSWorkspace sharedWorkspace].notificationCenter addObserver:self selector:@selector(volumeMountNotification:) name:NSWorkspaceDidMountNotification object:nil];
[[NSWorkspace sharedWorkspace].notificationCenter addObserver:self selector:@selector(volumeMountNotification:) name:NSWorkspaceDidUnmountNotification object:nil];
}
- (void)volumeMountNotification:(NSNotification*)sender {
//Delay upadting the menu to give the chance for the disk to fully mount or unmount
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self showActiveMicroBits];
});
}
#pragma mark - Menu bar app
- (void)createMenuBarIcon {
self.menubarItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.menubarItem.button.image = [NSImage imageNamed:@"menubar"];
}
- (void)showActiveMicroBits {
NSMenu * menu = [NSMenu new];
NSString * countString;
NSUInteger count = self.uploader.microBitPaths.count;
if (count == 0) {
countString = @"No connect micro:bits";
}
else if (count == 1) {
countString = @"1 connected micro:bit";
}
else {
countString = [NSString stringWithFormat:@"%lu connected micro:bits", count];
}
NSMenuItem * microBitCount = [[NSMenuItem alloc] initWithTitle:countString action:nil keyEquivalent:@""];
microBitCount.enabled = NO;
[menu addItem:microBitCount];
NSMenuItem * quitItem = [[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
[menu addItem:quitItem];
self.menubarItem.menu = menu;
}
@end

View File

@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16@2x.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32@2x.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128@2x.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256@2x.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "mac",
"filename" : "menubar.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,681 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
<connections>
<outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="Microbit Uploader" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Microbit Uploader" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About Microbit Uploader" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide Microbit Uploader" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit Microbit Uploader" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" id="KaW-ft-85H">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="-1" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="-1" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="Microbit Uploader Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<window title="Microbit Uploader" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="480" height="360"/>
<rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>

View File

@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
@class DirectoryWatcher;
@protocol DirectoryWatcherDelegate <NSObject>
- (void)watcher:(DirectoryWatcher*)watcher observedNewFileAtPath:(NSString*)path;
@end
@interface DirectoryWatcher : NSObject
- (instancetype)initWithPath:(NSString*)path;
@property (readonly) NSString * path;
@property id<DirectoryWatcherDelegate> delegate;
- (void)startWatching;
//Automatically called when deallocated
- (void)stopWatching;
@end

View File

@ -0,0 +1,73 @@
#import "DirectoryWatcher.h"
#import <CoreServices/CoreServices.h>
void callback(ConstFSEventStreamRef streamRef, void * info, size_t numEvents, void * eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
@interface DirectoryWatcher ()
@property NSString * path;
@property NSMutableSet<NSString*>* knownFiles;
@property FSEventStreamRef stream;
- (void)rescanPathWithEvents:(BOOL)sendEvents;
@end
@implementation DirectoryWatcher
- (instancetype)initWithPath:(NSString *)path {
if (!path) {
return nil;
}
self = [super init];
if (self) {
self.path = path;
}
return self;
}
- (void)dealloc {
[self stopWatching];
}
- (void)startWatching {
self.knownFiles = [NSMutableSet new];
[self rescanPathWithEvents:NO];
CFStringRef path = (__bridge CFStringRef)(self.path);
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void**)&path, 1, NULL);
CFAbsoluteTime latency = 1;
FSEventStreamContext context = { 0, (__bridge void * _Nullable)(self), NULL, NULL, NULL };
self.stream = FSEventStreamCreate(NULL, &callback, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNone);
FSEventStreamScheduleWithRunLoop(self.stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
FSEventStreamStart(self.stream);
}
- (void)stopWatching {
if (self.stream) {
FSEventStreamStop(self.stream);
FSEventStreamInvalidate(self.stream);
FSEventStreamRelease(self.stream);
self.stream = nil;
}
}
- (void)rescanPathWithEvents:(BOOL)sendEvents {
NSArray<NSString*>* downloadFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:nil];
for (NSString * file in downloadFiles) {
if (![self.knownFiles containsObject:file]) {
if (sendEvents) {
[self.delegate watcher:self observedNewFileAtPath:file];
}
[self.knownFiles addObject:file];
}
}
}
@end
void callback(ConstFSEventStreamRef streamRef, void * info, size_t numEvents, void * eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) {
DirectoryWatcher * watcher = (__bridge DirectoryWatcher*)info;
[watcher rescanPathWithEvents:YES];
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSUIElement</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Thomas Denney. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,21 @@
#import <Foundation/Foundation.h>
@class Uploader;
@protocol UploaderDelegate <NSObject>
- (void)uploader:(Uploader*)uploader transferredFile:(NSString*)file toMicroBit:(NSString*)microbit;
- (void)uploader:(Uploader*)uploader failedToTransferFile:(NSString*)file toMicroBit:(NSString*)microbit;
@end
@interface Uploader : NSObject
@property id<UploaderDelegate> delegate;
- (BOOL)shouldUploadFileAtPath:(NSString*)path;
- (NSArray<NSString*>*)microBitPaths;
- (void)uploadFile:(NSString*)file;
- (void)uploadFile:(NSString*)file toMicroBit:(NSString*)path;
@end

View File

@ -0,0 +1,74 @@
#import "Uploader.h"
@interface Uploader ()
@property NSOperationQueue * backgroundCopyQueue;
@end
@implementation Uploader
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundCopyQueue = [NSOperationQueue new];
}
return self;
}
- (BOOL)shouldUploadFileAtPath:(NSString *)path {
//Whilst Safari is downloading the file it appends .download to the name
NSRegularExpression * ignoreDownload = [NSRegularExpression regularExpressionWithPattern:@".download$" options:NSRegularExpressionCaseInsensitive error:nil];
if ([ignoreDownload numberOfMatchesInString:path.lastPathComponent options:0 range:NSMakeRange(0, path.lastPathComponent.length)] > 0) {
return NO;
}
//Chrome and Firefox create .hex files
NSRegularExpression * hexFiles = [NSRegularExpression regularExpressionWithPattern:@".hex$" options:NSRegularExpressionCaseInsensitive error:nil];
if ([hexFiles numberOfMatchesInString:path.lastPathComponent options:0 range:NSMakeRange(0, path.lastPathComponent.length)] > 0) {
return YES;
}
//Safari tends to just name files 'Unknown X'
NSRegularExpression * unknownFiles = [NSRegularExpression regularExpressionWithPattern:@"^Unknown(( |-)[0-9]+)?" options:NSRegularExpressionCaseInsensitive error:nil];
if ([unknownFiles numberOfMatchesInString:path.lastPathComponent options:0 range:NSMakeRange(0, path.lastPathComponent.length)]) {
return YES;
}
return NO;
}
- (NSArray<NSString*>*)microBitPaths {
NSArray<NSURL*>* allVolumes = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:nil options:NSVolumeEnumerationSkipHiddenVolumes];
NSMutableArray<NSString*>* microbitPaths = [NSMutableArray new];
NSRegularExpression * microbitRegex = [NSRegularExpression regularExpressionWithPattern:@"^MICROBIT" options:NSRegularExpressionCaseInsensitive error:nil];
for (NSURL * volume in allVolumes) {
NSString * lastPathComponent = volume.lastPathComponent;
if ([microbitRegex numberOfMatchesInString:lastPathComponent options:0 range:NSMakeRange(0, lastPathComponent.length)] > 0) {
[microbitPaths addObject:volume.path];
}
}
return microbitPaths;
}
- (void)uploadFile:(NSString *)file {
for (NSString * microbit in [self microBitPaths]) {
[self uploadFile:file toMicroBit:microbit];
}
}
- (void)uploadFile:(NSString *)file toMicroBit:(NSString *)path {
[self.backgroundCopyQueue addOperationWithBlock:^{
NSError * copyError;
NSString * destination = [path stringByAppendingPathComponent:file.lastPathComponent];
if (![[NSFileManager defaultManager] copyItemAtPath:file toPath:destination error:&copyError]) {
[self.delegate uploader:self failedToTransferFile:file toMicroBit:path.lastPathComponent];
}
else {
[self.delegate uploader:self transferredFile:file toMicroBit:path.lastPathComponent];
}
}];
}
@end

View File

@ -0,0 +1,5 @@
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}

View File

@ -0,0 +1,40 @@
# micro:bit uploader for OS X
![](Microbit Uploader/Assets.xcassets/AppIcon.appiconset/icon_256x256.png)
This project is a clone of the [Windows
uploader](https://codethemicrobit.com/uploader), but for OS X. Once launched,
the app runs in your menu bar and will automatically deploy any HEX files to
your `micro:bit`. Like the Windows version, it is compatible with any browser
that can run [codethemicrobit.com](http://codethemicrobit.com).
## Install the built version
1. Download the latest `.zip` release from the `Release` directory
2. Unzip it
3. Drag `Microbit Uploader` to your Applications folder and launch it
## Building
To build the project you'll need a copy of OS X 10.11 or higher and Xcode 8 or
higher (you may be able to build on earlier OSes or versions of Xcode, but this
remains untested). Once you have a development environment set up, just build
and run `Microbit Uploader.xcodeproj`.
## Distributing
1. Open the Xcode project
2. Product > Archive
3. Export:
![Export](Graphics/export.png)
4. You will then have the option of either signing or not-signing the
application:
a) If you have an Apple developer account, select 'Export a Developer
ID-signed Application'
b) If you don't have a developer ID, select 'Export as a macOS App'
5. Zip the produced app and upload to CDN or equivalent

Binary file not shown.

View File

@ -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>

5
docfiles/target.css Normal file
View File

@ -0,0 +1,5 @@
#root .avatar .avatar-image {
background-image: url(https://az851932.vo.msecnd.net/pub/jovrytni/microbit.simplified.svg);
background-size: contain;
background-repeat: no-repeat;
}

13
docfiles/tracking.html Normal file
View 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>

View File

@ -1,3 +1,50 @@
![](/static/mb/device/pano.jpg)
# About
### @description A Blocks / Javascript code editor for the micro:bit, a pocket-size computer with 5x5 display, sensors and Bluetooth.
The [BBC micro:bit](https://www.microbit.co.uk) is a [pocket-size computer](/device) with a 5x5 display of 25 LEDs, Bluetooth and sensors that can be programmed by anyone.
The BBC micro:bit was made possible by many [partners](https://www.microbit.co.uk/partners).
The micro:bit provides an easy and fun introduction to programming and making switch on, program it to do something fun wear it, customize it.
Just like Arduino, the micro:bit can be connected to and interact with sensors, displays, and other devices.
* [Read the docs](/docs)
## [Hardware: The Device](/device)
The BBC micro:bit is packaged with sensors, radio and other goodies. Learn about the [hardware components](/device) of the micro:bit to make the most of it!
## Programming: [Blocks](/blocks) or [JavaScript](/javascript)
You can program the micro:bit using [Blocks](/blocks) or [JavaScript](/javascript) in your web browser via the [micro:bit APIs](/reference):
```block
input.onButtonPressed(Button.A, () => {
basic.showString("Hi!");
})
```
```typescript
input.onButtonPressed(Button.A, () => {
basic.showString("Hi!");
})
```
The editor work in [most modern browsers](/browsers), work [offline](/offline) once loaded and do not require any installation.
## [Compile and Flash: Your Program!](/device/usb)
When you have your code ready, you connect your micro:bit to a computer via a USB cable, so it appears as a mounted drive (named MICROBIT).
Compilation to ARM thumb machine code from [Blocks](/blocks) or [JavaScript](/javascript) happens in the browser. You save the ARM binary
program to a file, which you then copy to the micro:bit drive, which flashes the micro:bit device with the new program.
## Simulator: Test Your Code
You can run your code using the micro:bit simulator, all within the confines of a web browser.
The simulator has support for the LED screen, buttons, as well as compass, accelerometer, and digital I/O pins.
```sim
basic.forever(() => {
basic.showString("Hi!");
@ -20,43 +67,8 @@ input.onButtonPressed(Button.B, () => {
. # . # .
. . # . .`);
});
```
# About
### @description A Blocks / Javascript code editor for the micro:bit, a pocket-size computer with 5x5 display, sensors and Bluetooth.
The [BBC micro:bit](https://www.microbit.co.uk) is a [pocket-size computer](/device) with a 5x5 display of 25 LEDs, Bluetooth and sensors that can be programmed by anyone.
The BBC micro:bit was made possible by many [partners](https://www.microbit.co.uk/partners).
The micro:bit provides an easy and fun introduction to programming and making switch on, program it to do something fun wear it, customize it.
Just like Arduino, the micro:bit can be connected to and interact with sensors, displays, and other devices.
## Hardware: The Device
Learn about the [hardware components](/device) of the micro:bit to make the most of it!
## Programming: Blocks or JavaScript
You can program the micro:bit using [Blocks](/blocks) or [JavaScript](/javascript), via the [micro:bit APIs](/reference):
```blocks
input.onButtonPressed(Button.A, () => {
basic.showString("Hi!");
})
```
## Compile and Flash: Your Program!
When you have your code ready, you connect your micro:bit to a computer via a USB cable, so it appears as a mounted drive (named MICROBIT).
Compilation to ARM thumb machine code from [Blocks](/blocks) or [JavaScript](/javascript) happens in the browser. You save the ARM binary
program to a file, which you then copy to the micro:bit drive, which flashes the micro:bit device with the new program.
## Simulator: Test Your Code
You can run your code using the micro:bit simulator, all within the confines of a web browser.
The simulator has support for the LED screen, buttons, as well as compass, accelerometer, and digital I/O pins.
## C++ Runtime
The [C++ micro:bit runtime](http://lancaster-university.github.io/microbit-docs/), created at [Lancaster University](http://www.lancaster.ac.uk/), provides access to the hardware functions of the micro:bit,
@ -65,6 +77,15 @@ as well as a set of helper functions (such as displaying a number/image/string o
The [micro:bit library](/reference) mirrors the functions of the C++ library.
When code is compiled to ARM machine code, the calls to JavaScript micro:bit functions are replaced with calls to the corresponding C++ functions.
## Open Source
## [Command Line Tools](/cli)
Looking to use codethemicrobit.com in your favorite editor? Install the [command line tools](/cli) and get rolling!
## [Packages](/packages)
Create, edit and distribute your own blocks and JavaScript using [packages](/packages). Packages are hosted on GitHub and may be written
using C++, JavaScript and/or ARM thumb.
## [Open Source](/open-source)
The code for the micro:bit is [open source](/open-source) on GitHub. Contributors are welcome!

101
docs/browsers.md Normal file
View File

@ -0,0 +1,101 @@
# Unsupported configuration
[codethemicrobit.com](https://codethemicrobit.com) doesn't currently support
your browser or operating system. The following configurations are supported:
## Windows
You need one of these browsers running on Windows 7, Windows 8, Windows 8.1, or
Windows 10:
* Internet Explorer 11
* Microsoft Edge
* Google Chrome
* Mozilla Firefox
## Mac
You need one of these browsers running on OS X 10.9 Mavericks, OS X 10.10
Yosemite, OS X 10.11 El Capitan, or macOS 10.12 Sierra:
* Safari
* Google Chrome
* Mozilla Firefox
## Linux
If you're using a Raspberry Pi, please see [the documentation
here](/raspberry-pi).
You need to be running a Linux distribution recent enough to run the most recent
version of one of the following:
* Google Chrome, or Chromium
* Mozilla Firefox, Iceweasel, or Seamonkey
## How to check your OS or browser
### Windows
* Click on the Start menu
* Type 'System'
* Click on the app called 'System'
* The version of Windows you are using will be displayed:
![](/static/configurations/windows-version.png)
### Mac
* Click on the Apple icon in the top left
* Click on 'About this Mac'
* This window will be displayed:
![](/static/configurations/osx-version.png)
### Internet Explorer
* Click on the Settings icon in the top right
* Click 'About Internet Explorer'
* This window will be displayed:
![](/static/configurations/ie-version.png)
### Edge
Edge automatically updates, so you should always be using the latest version
* Click on the menu icon in the top right (three dots)
* Scroll to the bottom
* Information similar to the following will be displayed:
![](/static/configurations/edge-version.png)
### Google Chrome
Google Chrome automatically updates, so you should always be using the latest version
* Click on the menu icon in the top right (three dots)
* Click Help, and About Google Chrome
* Information similar to the following will be displayed:
![](/static/configurations/chrome-version.png)
### Firefox
Firefox automatically updates, so you should always be using the latest version
* Click on the menu icon in the top right (three horizontal lines)
* Click the question mark icon (help button)
* Click 'About Firefox'
![](/static/configurations/firefox-version.png)
### Safari
Safari updates when your operating system updates, so if you are using the
latest version of OS X then you'll be using the latest version of Safari.
* Click on the Safari menu in the top left
* Click 'About Safari'
![](/static/configurations/safari-version.png)

6
docs/browsers/linux.md Normal file
View File

@ -0,0 +1,6 @@
# Unsupported configuration
As you are using Linux, it is recommended that you use Mozilla Firefox or Google
Chrome.
Please see [here](/browsers) for more information.

6
docs/browsers/mac.md Normal file
View File

@ -0,0 +1,6 @@
# Unsupported configuration
As you are using OS X, it is recommended that you use Safari. Alternatively,
Google Chrome and Mozilla Firefox are also supported.
Please see [here](/browsers) for more information.

8
docs/browsers/windows.md Normal file
View File

@ -0,0 +1,8 @@
# Unsupported configuration
As you are using Windows, it is recommended that you use Microsoft Edge. If you
are running a version of Windows prior to Windows 10, you can use Internet
Explorer 11. Alternatively, Google Chrome and Mozilla Firefox are also
supported.
Please see [here](/browsers) for more information.

View File

@ -69,7 +69,7 @@ Select **Save File** and then select **OK**.
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.
![](/static/mb/device/usb-windows-firefox-2.png)
![](/static/mb/device/usb-windows-firefox-2.jpg)
Drag and drop the `.hex` file from the download folder onto the `MICROBIT` drive.
@ -149,6 +149,10 @@ 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.
If you want to save time, you can use the [micro:bit uploader](/uploader) to
automatically deploy hex files to your micro:bit. It works on Windows and is
compatible with any browser.
## Troubleshooting
You cant drag and drop more than one hex file at once onto your micro:bit. If

View File

@ -58,7 +58,9 @@ After running this simulation several seconds by moving the micro:bit side to si
![](/static/mb/acc2.png)
### ~
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.
![](/static/mb/data3.png)
@ -93,4 +95,4 @@ NEXT: The Watch
```package
microbit-radio
```
```

29
docs/raspberry-pi.md Normal file
View 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 -
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
docs/static/mb/device/pano.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
docs/static/mb/device/usb-generic.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/static/mb/device/usb-mac.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -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."
}

View File

@ -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"

View File

@ -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.",

View File

@ -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",

View File

@ -42,6 +42,5 @@ namespace control {
* Display warning in the simulator.
*/
//% shim=pxtrt::runtimeWarning
export function runtimeWarning(message: string) {
}
export function runtimeWarning(message: string) { }
}

View File

@ -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
//

View File

@ -1,78 +1,126 @@
{
"ledmatrix": {
"visual": "ledmatrix",
"breadboardColumnsNeeded": 8,
"breadboardStartRow": "h",
"pinAllocation": {
"type": "auto",
"gpioPinsNeeded": [5, 5]
"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}
]
},
"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"]
"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"
},
"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}
"assembly": [
{"part": true},
{"pinIndices": [0, 1]},
{"pinIndices": [2, 3]}
]
},
"neopixel": {
"visual": "neopixel",
"breadboardColumnsNeeded": 5,
"breadboardStartRow": "h",
"pinAllocation": {
"type": "factoryfunction",
"functionName": "neopixel.create",
"pinArgPositions": [0],
"otherArgPositions": [1]
"simulationBehavior": "neopixel",
"visual": {
"builtIn": "neopixel",
"width": 58,
"height": 113,
"pinDistance": 9,
"pinLocations": [
{"x": 10, "y": 0},
{"x": 19, "y": 0},
{"x": 28, "y": 0}
]
},
"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}
"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,
"firstPin": [180, 135],
"pinDist": 70,
"extraColumnOffset": 1
"pinDistance": 70,
"pinLocations": [
{"x": 180, "y": 135},
{"x": 320, "y": 135}
]
},
"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}
"pinDefinitions": [
{"target": "P0", "style": "male", "orientation": "-Z"},
{"target": "ground", "style": "male", "orientation": "-Z"}
],
"instantiation": {"kind": "singleton"},
"assembly": [
{"part": true, "pinIndices": [0]},
{"pinIndices": [1]}
]
}
}

View File

@ -1,6 +1,6 @@
{
"name": "pxt-microbit",
"version": "0.3.77",
"version": "0.4.7",
"description": "micro:bit target for PXT",
"keywords": [
"JavaScript",
@ -29,6 +29,6 @@
"typescript": "^1.8.7"
},
"dependencies": {
"pxt-core": "0.3.90"
"pxt-core": "0.4.11"
}
}

View File

@ -75,7 +75,9 @@
},
"simulator": {
"autoRun": true,
"streams": true,
"aspectRatio": 1.22,
"parts": true,
"partsAspectRatio": 0.69,
"builtinParts": {
"accelerometer": true,
@ -85,13 +87,102 @@
"bluetooth": true,
"thermometer": true,
"compass": true
},
"boardDefinition": {
"visual": "microbit",
"gpioPinBlocks": [
[
"P0"
],
[
"P1"
],
[
"P2"
],
[
"P3"
],
[
"P4",
"P5",
"P6",
"P7"
],
[
"P8",
"P9",
"P10",
"P11",
"P12"
],
[
"P16"
]
],
"gpioPinMap": {
"P0": "P0",
"P1": "P1",
"P2": "P2",
"P3": "P3",
"P4": "P4",
"P5": "P5",
"P6": "P6",
"P7": "P7",
"P8": "P8",
"P9": "P9",
"P10": "P10",
"P11": "P11",
"P12": "P12",
"P13": "P13",
"P14": "P14",
"P15": "P15",
"P16": "P16",
"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,
"onboardComponents": [
"buttonpair",
"ledmatrix",
"speaker"
],
"useCrocClips": true,
"marginWhenBreadboarding": [
0,
0,
80,
0
]
}
},
"compileService": {
"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 +204,34 @@
"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",
"browserSupport": [
{
"name": "unsupported",
"os": "*",
"path": "/browsers"
},
{
"name": "unsupported",
"os": "mac",
"path": "/browsers/mac"
},
{
"name": "unsupported",
"os": "linux",
"path": "browsers/linux"
},
{
"name": "unsupported",
"os": "rpi",
"path": "/raspberry-pi"
},
{
"name": "unsupported",
"os": "windows",
"path": "/browsers/windows"
}
],
"boardName": "BBC micro:bit",
"docMenu": [
{
@ -140,10 +259,73 @@
"path": "/device"
}
],
"sideDoc": "getting-started"
"sideDoc": "getting-started",
"usbDocs": "/device/usb",
"usbHelp": [
{
"name": "connection",
"os": "*",
"browser": "*",
"path": "/static/mb/device/usb-generic.jpg"
},
{
"name": "connection",
"os": "mac",
"browser": "*",
"path": "/static/mb/device/usb-mac.jpg"
},
{
"name": "save",
"os": "windows",
"browser": "firefox",
"path": "/static/mb/device/usb-windows-firefox-1.png"
},
{
"name": "save",
"os": "mac",
"browser": "firefox",
"path": "/static/mb/device/usb-osx-firefox-1.png"
},
{
"name": "save",
"os": "mac",
"browser": "chrome",
"path": "/static/mb/device/usb-osx-chrome.png"
},
{
"name": "save",
"os": "windows",
"browser": "edge",
"path": "/static/mb/device/usb-windows-edge-1.png"
},
{
"name": "save",
"os": "windows",
"browser": "ie",
"path": "/static/mb/device/usb-windows-ie11-1.png"
},
{
"name": "save",
"os": "windows",
"browser": "chrome",
"path": "/static/mb/device/usb-windows-chrome.png"
},
{
"name": "copy",
"os": "mac",
"browser": "*",
"path": "/static/mb/device/usb-osx-dnd.png"
},
{
"name": "copy",
"os": "windows",
"browser": "*",
"path": "/static/mb/device/usb-windows-sendto.jpg"
}
]
},
"analytics": {
"userVoiceApiKey": "WEkkIGaj1WtJnSUF59iwaA",
"userVoiceForumId": 402381
}
}
}

View File

@ -1,556 +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[]
}
interface PowerUsage {
topGround: boolean,
topThreeVolt: boolean,
bottomGround: boolean,
bottomThreeVolt: boolean,
singleGround: boolean,
singleThreeVolt: boolean,
}
function isOnBreadboardBottom(location: WireLocationDefinition) {
let isBot = false;
if (location[0] === "breadboard") {
let row = <string>location[1];
isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row);
}
return isBot;
}
const arrCount = (a: boolean[]) => a.reduce((p, n) => p + (n ? 1 : 0), 0);
const arrAny = (a: boolean[]) => arrCount(a) > 0;
function computePowerUsage(wireDef: WireDefinition): PowerUsage {
let ends = [wireDef.start, wireDef.end];
let endIsGround = ends.map(e => e === "ground");
let endIsThreeVolt = ends.map(e => e === "threeVolt");
let endIsBot = ends.map(e => isOnBreadboardBottom(e));
let hasGround = arrAny(endIsGround);
let hasThreeVolt = arrAny(endIsThreeVolt);
let hasBot = arrAny(endIsBot);
return {
topGround: hasGround && !hasBot,
topThreeVolt: hasThreeVolt && !hasBot,
bottomGround: hasGround && hasBot,
bottomThreeVolt: hasThreeVolt && hasBot,
singleGround: hasGround,
singleThreeVolt: hasThreeVolt
};
}
function mergePowerUsage(powerUsages: PowerUsage[]) {
let finalPowerUsage = powerUsages.reduce((p, n) => ({
topGround: p.topGround || n.topGround,
topThreeVolt: p.topThreeVolt || n.topThreeVolt,
bottomGround: p.bottomGround || n.bottomGround,
bottomThreeVolt: p.bottomThreeVolt || n.bottomThreeVolt,
singleGround: n.singleGround ? p.singleGround === null : p.singleGround,
singleThreeVolt: n.singleThreeVolt ? p.singleThreeVolt === null : p.singleThreeVolt,
}), {
topGround: false,
topThreeVolt: false,
bottomGround: false,
bottomThreeVolt: false,
singleGround: null,
singleThreeVolt: null,
});
if (finalPowerUsage.singleGround)
finalPowerUsage.topGround = finalPowerUsage.bottomGround = false;
if (finalPowerUsage.singleThreeVolt)
finalPowerUsage.topThreeVolt = finalPowerUsage.bottomThreeVolt = false;
return finalPowerUsage;
}
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}`]),
},
};
private powerUsage: PowerUsage;
constructor(opts: AllocatorOpts) {
this.opts = opts;
}
private allocateLocation(location: WireLocationDefinition, opts: AllocLocOpts): Loc {
if (location === "ground" || location === "threeVolt") {
//special case if there is only a single ground or three volt pin in the whole build
if (location === "ground" && this.powerUsage.singleGround) {
let boardGroundPin = this.getBoardGroundPin();
return {type: "dalboard", pin: boardGroundPin};
} else if (location === "threeVolt" && this.powerUsage.singleThreeVolt) {
let boardThreeVoltPin = this.getBoardThreeVoltPin();
return {type: "dalboard", pin: boardThreeVoltPin};
}
U.assert(!!opts.nearestBBPin);
let nearestCoord = this.opts.getBBCoord(opts.nearestBBPin);
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 barPins: BBRowCol[];
if (nearTop) {
if (location === "ground") {
barPins = this.availablePowerPins.top.ground;
} else if (location === "threeVolt") {
barPins = this.availablePowerPins.top.threeVolt;
}
} else {
if (location === "ground") {
barPins = this.availablePowerPins.bottom.ground;
} else if (location === "threeVolt") {
barPins = this.availablePowerPins.bottom.threeVolt;
}
}
let pinCoords = barPins.map(rowCol => {
return this.opts.getBBCoord(rowCol);
});
let closestPinIdx = visuals.findClosestCoordIdx(nearestCoord, pinCoords);
let pin = barPins[closestPinIdx];
if (nearTop) {
this.availablePowerPins.top.ground.splice(closestPinIdx, 1);
this.availablePowerPins.top.threeVolt.splice(closestPinIdx, 1);
} else {
this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1);
this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 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 if (location === "MOSI" || location === "MISO" || location === "SCK") {
if (!this.opts.boardDef.spiPins)
console.debug("No SPI pin mappings found!");
let pin = (<any>this.opts.boardDef.spiPins)[location as string] as string;
return {type: "dalboard", pin: pin};
} else if (location === "SDA" || location === "SCL") {
if (!this.opts.boardDef.i2cPins)
console.debug("No I2C pin mappings found!");
let pin = (<any>this.opts.boardDef.i2cPins)[location as string] as string;
return {type: "dalboard", pin: pin};
} else {
//TODO
U.assert(false);
return null;
}
}
private getBoardGroundPin() {
let boardGround = this.opts.boardDef.groundPins[0] || null;
if (!boardGround) {
console.log("No available ground pin on board!");
//TODO
}
return boardGround;
}
private getBoardThreeVoltPin() {
let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
if (!threeVoltPin) {
console.log("No available 3.3V pin on board!");
//TODO
}
return threeVoltPin;
}
private allocatePowerWires(powerUsage: PowerUsage): WireInst[] {
let boardGroundPin = this.getBoardGroundPin();
let threeVoltPin = this.getBoardThreeVoltPin();
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[] = [];
let groundStep = 0;
let threeVoltStep = (powerUsage.bottomGround || powerUsage.topGround) ? 1 : 0;
if (powerUsage.bottomGround && powerUsage.topGround) {
//bb top - <==> bb bot -
wires.push({
start: this.allocateLocation("ground", {nearestBBPin: top}),
end: this.allocateLocation("ground", {nearestBBPin: bot}),
color: GROUND_COLOR, assemblyStep: groundStep
});
}
if (powerUsage.topGround) {
//board - <==> bb top -
wires.push({
start: this.allocateLocation("ground", {nearestBBPin: top}),
end: {type: "dalboard", pin: boardGroundPin},
color: GROUND_COLOR, assemblyStep: groundStep
});
} else if (powerUsage.bottomGround) {
//board - <==> bb bot -
wires.push({
start: this.allocateLocation("ground", {nearestBBPin: bot}),
end: {type: "dalboard", pin: boardGroundPin},
color: GROUND_COLOR, assemblyStep: groundStep
});
}
if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) {
//bb top + <==> bb bot +
wires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
end: this.allocateLocation("threeVolt", {nearestBBPin: bot}),
color: POWER_COLOR, assemblyStep: threeVoltStep
});
}
if (powerUsage.topThreeVolt) {
//board + <==> bb top +
wires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: top}),
end: {type: "dalboard", pin: threeVoltPin},
color: POWER_COLOR, assemblyStep: threeVoltStep
});
} else if (powerUsage.bottomThreeVolt) {
//board + <==> bb bot +
wires.push({
start: this.allocateLocation("threeVolt", {nearestBBPin: bot}),
end: {type: "dalboard", pin: threeVoltPin},
color: POWER_COLOR, assemblyStep: threeVoltStep
});
}
return wires;
}
private allocateWire(wireDef: WireDefinition, opts: AllocWireOpts): WireInst {
let ends = [wireDef.start, wireDef.end];
let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
//allocate non-power first so we know the nearest pin for the power end
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocateLocation(e, opts) : null)
//allocate power pins closest to the other end of the wire
endInsts = endInsts.map((e, idx) => {
if (e)
return e;
let locInst = <BBLoc>endInsts[1 - idx]; // non-power end
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) {
let partialCmps = this.allocatePartialCmps();
let allWireDefs = partialCmps.map(p => p.def.wires).reduce((p, n) => p.concat(n), []);
let allPowerUsage = allWireDefs.map(w => computePowerUsage(w));
this.powerUsage = mergePowerUsage(allPowerUsage);
basicWires = this.allocatePowerWires(this.powerUsage);
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();
}
}

View File

@ -1,12 +1,7 @@
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
namespace pxsim {
export class DalBoard extends BaseBoard {
id: string;
// the bus
bus: EventBus;
export class DalBoard extends CoreBoard {
// state & update logic for component services
ledMatrixState: LedMatrixState;
edgeConnectorState: EdgeConnectorState;
@ -19,31 +14,58 @@ namespace pxsim {
radioState: RadioState;
neopixelState: NeoPixelState;
// updates
updateSubscribers: (() => void)[];
constructor() {
super()
this.id = "b" + Math_.random(2147483647);
this.bus = new EventBus(runtime);
// components
this.ledMatrixState = new LedMatrixState(runtime);
this.buttonPairState = new ButtonPairState();
this.edgeConnectorState = new EdgeConnectorState();
this.radioState = new RadioState(runtime);
this.accelerometerState = new AccelerometerState(runtime);
this.serialState = new SerialState();
this.thermometerState = new ThermometerState();
this.lightSensorState = new LightSensorState();
this.compassState = new CompassState();
this.neopixelState = new NeoPixelState();
this.builtinParts["ledmatrix"] = this.ledMatrixState = new LedMatrixState(runtime);
this.builtinParts["buttonpair"] = 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.builtinParts["edgeconnector"] = this.edgeConnectorState = new EdgeConnectorState({
pins: [
DAL.MICROBIT_ID_IO_P0,
DAL.MICROBIT_ID_IO_P1,
DAL.MICROBIT_ID_IO_P2,
DAL.MICROBIT_ID_IO_P3,
DAL.MICROBIT_ID_IO_P4,
DAL.MICROBIT_ID_IO_P5,
DAL.MICROBIT_ID_IO_P6,
DAL.MICROBIT_ID_IO_P7,
DAL.MICROBIT_ID_IO_P8,
DAL.MICROBIT_ID_IO_P9,
DAL.MICROBIT_ID_IO_P10,
DAL.MICROBIT_ID_IO_P11,
DAL.MICROBIT_ID_IO_P12,
DAL.MICROBIT_ID_IO_P13,
DAL.MICROBIT_ID_IO_P14,
DAL.MICROBIT_ID_IO_P15,
DAL.MICROBIT_ID_IO_P16,
0,
0,
DAL.MICROBIT_ID_IO_P19,
DAL.MICROBIT_ID_IO_P20
]
});
this.builtinParts["radio"] = this.radioState = new RadioState(runtime);
this.builtinParts["accelerometer"] = this.accelerometerState = new AccelerometerState(runtime);
this.builtinParts["serial"] = this.serialState = new SerialState();
this.builtinParts["thermometer"] = this.thermometerState = new ThermometerState();
this.builtinParts["lightsensor"] = this.lightSensorState = new LightSensorState();
this.builtinParts["compass"] = this.compassState = new CompassState();
this.builtinParts["neopixel"] = this.neopixelState = new NeoPixelState();
// updates
this.updateSubscribers = []
this.updateView = () => {
this.updateSubscribers.forEach(sub => sub());
}
this.builtinVisuals["buttonpair"] = () => new visuals.ButtonPairView();
this.builtinVisuals["ledmatrix"] = () => new visuals.LedMatrixView();
this.builtinVisuals["neopixel"] = () => new visuals.NeoPixelView();
this.builtinPartVisuals["buttonpair"] = (xy: visuals.Coord) => visuals.mkBtnSvg(xy);
this.builtinPartVisuals["ledmatrix"] = (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8);
this.builtinPartVisuals["neopixel"] = (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy);
}
receiveMessage(msg: SimulatorMessage) {
@ -65,31 +87,26 @@ namespace pxsim {
}
}
kill() {
super.kill();
AudioContextManager.stop();
}
initAsync(msg: SimulatorRunMessage): Promise<void> {
super.initAsync(msg);
let options = (msg.options || {}) as RuntimeOptions;
const options = (msg.options || {}) as RuntimeOptions;
let boardDef = CURRENT_BOARD; //TODO: read from pxt.json/pxttarget.json
const boardDef = msg.boardDefinition;
const cmpsList = msg.parts;
const cmpDefs = msg.partDefinitions || {};
const fnArgs = msg.fnArgs;
let cmpsList = msg.parts;
let cmpDefs = msg.partDefinitions || {}; //TODO: read from pxt.json/pxttarget.json
let fnArgs = msg.fnArgs;
let viewHost = new visuals.BoardHost({
const opts : visuals.BoardHostOpts = {
state: this,
boardDef: boardDef,
cmpsList: cmpsList,
cmpDefs: cmpDefs,
partsList: cmpsList,
partDefs: cmpDefs,
fnArgs: fnArgs,
maxWidth: "100%",
maxHeight: "100%",
});
};
const viewHost = new visuals.BoardHost(pxsim.visuals.mkBoardView(opts), opts);
document.body.innerHTML = ""; // clear children
document.body.appendChild(viewHost.getView());
@ -97,4 +114,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;
}
}

View File

@ -1,81 +0,0 @@
/// <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 const MICROBIT_DEF: BoardDefinition = {
visual: "microbit",
gpioPinBlocks: [
["P0"], ["P1"], ["P2"],
["P3"],
["P4", "P5", "P6", "P7"],
["P8", "P9", "P10", "P11", "P12"],
["P16"],
],
gpioPinMap: {
"P0": "P0",
"P1": "P1",
"P2": "P2",
"P3": "P3",
"P4": "P4",
"P5": "P5",
"P6": "P6",
"P7": "P7",
"P8": "P8",
"P9": "P9",
"P10": "P10",
"P11": "P11",
"P12": "P12",
"P13": "P13",
"P14": "P14",
"P15": "P15",
"P16": "P16",
"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,
onboardComponents: ["buttonpair", "ledmatrix", "speaker"],
useCrocClips: true,
marginWhenBreadboarding: [0, 0, 80, 0],
}
export const builtinComponentSimVisual: Map<() => visuals.IBoardComponent<any>> = {
"buttonpair": () => new visuals.ButtonPairView(),
"ledmatrix": () => new visuals.LedMatrixView(),
"neopixel": () => new visuals.NeoPixelView(),
};
export const builtinComponentSimState: Map<(d: DalBoard) => any> = {
"buttonpair": (d: DalBoard) => d.buttonPairState,
"ledmatrix": (d: DalBoard) => d.ledMatrixState,
"edgeconnector": (d: DalBoard) => d.edgeConnectorState,
"serial": (d: DalBoard) => d.serialState,
"radio": (d: DalBoard) => d.radioState,
"thermometer": (d: DalBoard) => d.thermometerState,
"accelerometer": (d: DalBoard) => d.accelerometerState,
"compass": (d: DalBoard) => d.compassState,
"lightsensor": (d: DalBoard) => d.lightSensorState,
"neopixel": (d: DalBoard) => d.neopixelState,
};
export const builtinComponentPartVisual: Map<(xy: visuals.Coord) => visuals.SVGElAndSize> = {
"buttonpair": (xy: visuals.Coord) => visuals.mkBtnSvg(xy),
"ledmatrix": (xy: visuals.Coord) => visuals.mkLedMatrixSvg(xy, 8, 8),
"neopixel": (xy: visuals.Coord) => visuals.mkNeoPixelPart(xy),
};
//TODO: add multiple board support
export const CURRENT_BOARD = MICROBIT_DEF;
}

View File

@ -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,7 +17,7 @@ namespace pxsim.instructions {
const LBL_LEFT_PAD = 5;
const REQ_WIRE_HEIGHT = 45;
const REQ_CMP_HEIGHT = 55;
const REQ_CMP_SCALE = 0.5 * 4;
const REQ_CMP_SCALE = 0.5 * 3;
type Orientation = "landscape" | "portrait";
const ORIENTATION: Orientation = "portrait";
const PPI = 96.0;
@ -59,7 +56,7 @@ namespace pxsim.instructions {
border-color: ${BORDER_COLOR};
border-style: solid;
border-radius: ${BORDER_RADIUS}px;
display: block;
display: inline-block;
width: ${PANEL_WIDTH}px;
height: ${PANEL_HEIGHT}px;
position: relative;
@ -284,18 +281,19 @@ 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 state = runtime.board as pxsim.CoreBoard;
let el: visuals.SVGElAndSize;
if (cmp == "wire") {
//TODO: support non-croc wire parts
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", opts.crocClips);
} else if (typeof cmp == "string") {
let builtinVis = <string>cmp;
let cnstr = builtinComponentPartVisual[builtinVis];
el = cnstr([0, 0]);
} else {
let partVis = <PartVisualDefinition>cmp;
el = visuals.mkGenericPartSVG(partVis);
if (typeof partVis.builtIn == "string") {
let cnstr = state.builtinPartVisuals[partVis.builtIn];
el = cnstr([0, 0]);
} else {
el = visuals.mkGenericPartSVG(partVis);
}
}
return wrapSvg(el, opts);
}
@ -305,40 +303,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 = powerWires.length > 0 ? getMaxStep(powerWires) + 2 : 1;
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 => {
@ -350,7 +341,7 @@ namespace pxsim.instructions {
});
return {
boardDef: allocOpts.boardDef,
cmpDefs: allocOpts.cmpDefs,
cmpDefs: allocOpts.partDefs,
fnArgs: allocOpts.fnArgs,
allAlloc: allocRes,
stepToWires: stepToWires,
@ -363,16 +354,17 @@ 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({
const state = runtime.board as pxsim.CoreBoard;
const opts : visuals.BoardHostOpts = {
state: state,
boardDef: boardDef,
forceBreadboard: true,
cmpDefs: cmpDefs,
partDefs: cmpDefs,
maxWidth: `${width}px`,
fnArgs: fnArgs,
wireframe: buildMode,
});
};
let boardHost = new visuals.BoardHost(pxsim.visuals.mkBoardView(opts), opts);
let view = boardHost.getView();
svg.addClass(view, "board-svg");
@ -397,6 +389,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 => {
@ -405,13 +410,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);
}
@ -420,24 +424,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() {
@ -463,7 +449,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, {
@ -516,7 +502,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;
@ -536,17 +522,23 @@ namespace pxsim.instructions {
});
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
@ -623,6 +615,9 @@ ${tsPackage}
});
}
// board def
const boardDef = JSON.parse(getQsVal("board")) as pxsim.BoardDefinition;
//parts list
let parts = (getQsVal("parts") || "").split(" ");
parts.sort();
@ -646,7 +641,6 @@ ${tsPackage}
style.textContent += STYLE;
const boardDef = CURRENT_BOARD;
const cmpDefs = partDefinitions;
//props
@ -656,8 +650,8 @@ ${tsPackage}
activeComponents.sort();
let props = mkBoardProps({
boardDef: boardDef,
cmpDefs: cmpDefs,
cmpList: activeComponents,
partDefs: cmpDefs,
partsList: activeComponents,
fnArgs: fnArgs,
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
});

View File

@ -1,261 +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;
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;
}
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;
}
}

View File

@ -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);
}
}
}

View File

@ -12,11 +12,4 @@ namespace pxsim.input {
// TODO
return 0;
}
}
namespace pxsim {
export class CompassState {
usesHeading = false;
heading = 90;
}
}

View File

@ -24,64 +24,6 @@ namespace pxsim {
export function getPin(id: number) {
return board().edgeConnectorState.getPin(id);
}
export enum PinFlags {
Unused = 0,
Digital = 0x0001,
Analog = 0x0002,
Input = 0x0004,
Output = 0x0008,
Touch = 0x0010
}
export class Pin {
constructor(public id: number) { }
touched = false;
value = 0;
period = 0;
mode = PinFlags.Unused;
pitch = false;
pull = 0; // PullDown
isTouched(): boolean {
this.mode = PinFlags.Touch;
return this.touched;
}
}
export class EdgeConnectorState {
pins: Pin[];
constructor() {
this.pins = [
new Pin(DAL.MICROBIT_ID_IO_P0),
new Pin(DAL.MICROBIT_ID_IO_P1),
new Pin(DAL.MICROBIT_ID_IO_P2),
new Pin(DAL.MICROBIT_ID_IO_P3),
new Pin(DAL.MICROBIT_ID_IO_P4),
new Pin(DAL.MICROBIT_ID_IO_P5),
new Pin(DAL.MICROBIT_ID_IO_P6),
new Pin(DAL.MICROBIT_ID_IO_P7),
new Pin(DAL.MICROBIT_ID_IO_P8),
new Pin(DAL.MICROBIT_ID_IO_P9),
new Pin(DAL.MICROBIT_ID_IO_P10),
new Pin(DAL.MICROBIT_ID_IO_P11),
new Pin(DAL.MICROBIT_ID_IO_P12),
new Pin(DAL.MICROBIT_ID_IO_P13),
new Pin(DAL.MICROBIT_ID_IO_P14),
new Pin(DAL.MICROBIT_ID_IO_P15),
new Pin(DAL.MICROBIT_ID_IO_P16),
null,
null,
new Pin(DAL.MICROBIT_ID_IO_P19),
new Pin(DAL.MICROBIT_ID_IO_P20)
];
}
public getPin(id: number) {
return this.pins.filter(p => p && p.id == id)[0] || null
}
}
}
namespace pxsim.pins {

View File

@ -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 {
@ -337,6 +271,7 @@ namespace pxsim.led {
export function stopAnimation(): void {
board().ledMatrixState.animationQ.cancelAll();
board().ledMatrixState.image.clear();
}
export function setDisplayMode(mode: DisplayMode): void {

View File

@ -1,10 +1,3 @@
namespace pxsim {
export class LightSensorState {
usesLightLevel = false;
lightLevel = 128;
}
}
namespace pxsim.input {
export function lightLevel(): number {
let b = board().lightSensorState;

View File

@ -22,80 +22,9 @@ namespace pxsim {
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 {

View File

@ -4,54 +4,10 @@ namespace pxsim {
if (b) {
let np = b.neopixelState;
if (np) {
np.updateBuffer(buffer, pin);
let buf = <Uint8Array[]>(<any>buffer).data;
np.updateBuffer(buf, pin);
runtime.queueDisplayUpdate();
}
}
}
}
namespace pxsim {
export enum NeoPixelMode {RGB, RGBW};
export type RGBW = [number, number, number, number];
function readNeoPixelBuffer(inBuffer: Uint8Array[], outColors: RGBW[], mode: NeoPixelMode) {
let buf = inBuffer;
let stride = mode === NeoPixelMode.RGBW ? 4 : 3;
let pixelCount = Math.floor(buf.length / stride);
for (let i = 0; i < pixelCount; i++) {
// NOTE: for whatever reason, NeoPixels pack GRB not RGB
let r = buf[i * stride + 1] as any as number
let g = buf[i * stride + 0] as any as number
let b = buf[i * stride + 2] as any as number
let w = 0;
if (stride === 4)
w = buf[i * stride + 3] as any as number
outColors[i] = [r, g, b, w]
}
}
export class NeoPixelState {
private buffers: {[pin: number]: Uint8Array[]} = {};
private colors: {[pin: number]: RGBW[]} = {};
private dirty: {[pin: number]: boolean} = {};
public updateBuffer(buffer: Buffer, pin: DigitalPin) {
//update buffers
let buf = <Uint8Array[]>(<any>buffer).data;
this.buffers[pin] = buf;
this.dirty[pin] = true;
}
public getColors(pin: number, mode: NeoPixelMode): RGBW[] {
let outColors = this.colors[pin] || (this.colors[pin] = []);
if (this.dirty[pin]) {
let buf = this.buffers[pin] || (this.buffers[pin] = []);
readNeoPixelBuffer(buf, outColors, mode);
this.dirty[pin] = false;
}
return outColors;
}
}
}

View File

@ -1,193 +0,0 @@
namespace pxsim.visuals {
export interface BoardHostOpts {
state: DalBoard,
boardDef: BoardDefinition,
cmpsList?: string[],
cmpDefs: Map<PartDefinition>,
fnArgs: any,
forceBreadboard?: boolean,
maxWidth?: string,
maxHeight?: string
wireframe?: boolean
}
export class BoardHost {
private components: IBoardComponent<any>[] = [];
private wireFactory: WireFactory;
private breadboard: Breadboard;
private fromBBCoord: (xy: Coord) => Coord;
private fromMBCoord: (xy: Coord) => Coord;
private boardView: BoardView;
private view: SVGSVGElement;
private style: SVGStyleElement;
private defs: SVGDefsElement;
private state: DalBoard;
private useCrocClips: boolean;
constructor(opts: BoardHostOpts) {
this.state = opts.state;
let onboardCmps = opts.boardDef.onboardComponents || [];
let activeComponents = (opts.cmpsList || []).filter(c => onboardCmps.indexOf(c) < 0);
activeComponents.sort();
this.useCrocClips = opts.boardDef.useCrocClips;
if (opts.boardDef.visual === "microbit") {
this.boardView = new visuals.MicrobitBoardSvg({
runtime: runtime,
theme: visuals.randomTheme(),
disableTilt: false,
wireframe: opts.wireframe,
});
} else {
let boardVis = opts.boardDef.visual as BoardImageDefinition;
this.boardView = new visuals.GenericBoardSvg({
visualDef: boardVis,
wireframe: opts.wireframe,
});
}
let useBreadboard = 0 < activeComponents.length || opts.forceBreadboard;
if (useBreadboard) {
this.breadboard = new Breadboard({
wireframe: opts.wireframe,
});
let bMarg = opts.boardDef.marginWhenBreadboarding || [0, 0, 40, 0];
let composition = composeSVG({
el1: this.boardView.getView(),
scaleUnit1: this.boardView.getPinDist(),
el2: this.breadboard.getSVGAndSize(),
scaleUnit2: this.breadboard.getPinDist(),
margin: [bMarg[0], bMarg[1], 20, bMarg[3]],
middleMargin: bMarg[2],
maxWidth: opts.maxWidth,
maxHeight: opts.maxHeight,
});
let under = composition.under;
let over = composition.over;
this.view = composition.host;
let edges = composition.edges;
this.fromMBCoord = composition.toHostCoord1;
this.fromBBCoord = composition.toHostCoord2;
let pinDist = composition.scaleUnit;
this.style = <SVGStyleElement>svg.child(this.view, "style", {});
this.defs = <SVGDefsElement>svg.child(this.view, "defs", {});
this.wireFactory = new WireFactory(under, over, edges, this.style, this.getLocCoord.bind(this));
let allocRes = allocateDefinitions({
boardDef: opts.boardDef,
cmpDefs: opts.cmpDefs,
fnArgs: opts.fnArgs,
getBBCoord: this.breadboard.getCoord.bind(this.breadboard),
cmpList: activeComponents,
});
this.addAll(allocRes);
} else {
let el = this.boardView.getView().el;
this.view = el;
if (opts.maxWidth)
svg.hydrate(this.view, { width: opts.maxWidth });
if (opts.maxHeight)
svg.hydrate(this.view, { height: opts.maxHeight });
}
this.state.updateSubscribers.push(() => this.updateState());
}
public highlightBoardPin(pinNm: string) {
this.boardView.highlightPin(pinNm);
}
public highlightBreadboardPin(rowCol: BBRowCol) {
this.breadboard.highlightLoc(rowCol);
}
public highlightWire(wire: Wire) {
//TODO: move to wiring.ts
//underboard wires
wire.wires.forEach(e => {
svg.addClass(e, "highlight");
(<any>e).style["visibility"] = "visible";
});
//un greyed out
svg.addClass(wire.endG, "highlight");
}
public getView(): SVGElement {
return this.view;
}
private updateState() {
this.components.forEach(c => c.updateState());
}
private getBBCoord(rowCol: BBRowCol) {
let bbCoord = this.breadboard.getCoord(rowCol);
return this.fromBBCoord(bbCoord);
}
private getPinCoord(pin: string) {
let boardCoord = this.boardView.getCoord(pin);
return this.fromMBCoord(boardCoord);
}
public getLocCoord(loc: Loc): Coord {
let coord: Coord;
if (loc.type === "breadboard") {
let rowCol = (<BBLoc>loc).rowCol;
coord = this.getBBCoord(rowCol);
} else {
let pinNm = (<BoardLoc>loc).pin;
coord = this.getPinCoord(pinNm);
}
if (!coord) {
console.error("Unknown location: " + name)
return [0, 0];
}
return coord;
}
public addComponent(cmpDesc: CmpInst): IBoardComponent<any> {
let cmp: IBoardComponent<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);
} else {
let vis = cmpDesc.visual as PartVisualDefinition;
cmp = new GenericPart(vis);
colOffset = vis.extraColumnOffset || 0;
}
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}`];
let coord = this.getBBCoord(rowCol);
cmp.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;
}
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);
});
}
}
}

19
sim/visuals/boardview.ts Normal file
View File

@ -0,0 +1,19 @@
namespace pxsim.visuals {
export function mkBoardView(opts: BoardHostOpts): BoardView {
if (opts.boardDef.visual === "microbit") {
return new visuals.MicrobitBoardSvg({
runtime: runtime,
theme: visuals.randomTheme(),
disableTilt: false,
wireframe: opts.wireframe,
});
} else {
let boardVis = opts.boardDef.visual as BoardImageDefinition;
return new visuals.GenericBoardSvg({
visualDef: boardVis,
wireframe: opts.wireframe,
});
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
})
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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 { }
}
}

View File

@ -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[];

View File

@ -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;

View File

@ -2,45 +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 {
//expects rgb from 0,255, gives h in [0,360], s in [0, 100], l in [0, 100]
export function rgbToHsl(rgb: [number, number, number]): [number, number, number] {
let [r, g, b] = rgb;
let [r$, g$, b$] = [r / 255, g / 255, b / 255];
let cMin = Math.min(r$, g$, b$);
let cMax = Math.max(r$, g$, b$);
let cDelta = cMax - cMin;
let h: number, s: number, l: number;
let maxAndMin = cMax + cMin;
//lum
l = (maxAndMin / 2) * 100
if (cDelta === 0)
s = h = 0;
else {
//hue
if (cMax === r$)
h = 60 * (((g$ - b$) / cDelta) % 6);
else if (cMax === g$)
h = 60 * (((b$ - r$) / cDelta) + 2);
else if (cMax === b$)
h = 60 * (((r$ - g$) / cDelta) + 4);
//sat
if (l > 50)
s = 100 * (cDelta / (2 - maxAndMin));
else
s = 100 * (cDelta / maxAndMin);
}
return [Math.floor(h), Math.floor(s), Math.floor(l)];
}
}
namespace pxsim.visuals {
const PIXEL_SPACING = PIN_DIST * 3;
@ -102,34 +63,26 @@ namespace pxsim.visuals {
});
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;
}
public setRgb(rgb: [number, number, number]) {
let hsl = rgbToHsl(rgb);
let hsl = visuals.rgbToHsl(rgb);
let [h, s, l] = hsl;
//We ignore luminosity since it doesn't map well to real-life brightness
let fill = `hsl(${h}, ${s}%, 70%)`;
// at least 70% luminosity
l = Math.max(l, 60);
let fill = `hsl(${h}, ${s}%, ${l}%)`;
this.el.setAttribute("fill", fill);
}
}
@ -200,29 +153,26 @@ namespace pxsim.visuals {
}
};
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 {
const modeMap: Map<NeoPixelMode> = {
"NeoPixelMode.RGB": NeoPixelMode.RGB,
"NeoPixelMode.RGBW": NeoPixelMode.RGBW,
"*": NeoPixelMode.RGB,
"NeoPixelMode.RGBW": NeoPixelMode.RGBW
};
let mode: NeoPixelMode = null;
for (let key in modeMap) {
if (key == modeStr) {
mode = modeMap[key];
break;
}
}
U.assert(mode != null, "Unknown NeoPixelMode: " + modeStr);
return mode;
return modeMap[modeStr] || NeoPixelMode.RGB;
}
export class NeoPixelView implements IBoardComponent<NeoPixelState> {
export class NeoPixelView implements IBoardPart<NeoPixelState> {
public style: string = `
.sim-neopixel-canvas {
}
@ -240,6 +190,7 @@ namespace pxsim.visuals {
}
`;
public element: SVGElement;
public overElement: SVGElement;
public defs: SVGElement[];
private state: NeoPixelState;
private canvas: NeoPixelCanvas;
@ -249,22 +200,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();
}
@ -276,6 +229,7 @@ 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.
}

View File

@ -1,470 +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 mkSmallMBPinEnd(p: [number, number], top: boolean, clr: string): visuals.SVGElAndSize {
//HACK
//TODO: merge with mkOpenJumperEnd()
let k = visuals.PIN_DIST * 0.24;
let plasticLength = k * 4;
let plasticWidth = k * 1.2;
let metalLength = k * 10;
let metalWidth = k;
const strokeWidth = visuals.PIN_DIST / 4.0;
let [cx, cy] = p;
let yOffset = 10;
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 + yOffset - (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 + yOffset + 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, smallPin: boolean = false): 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: SVGElAndSize;
if (smallPin)
end2AndSize = mkSmallMBPinEnd(endCoord2, true, color);
else
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") {
let boardPin = (<BoardLoc>end).pin;
if (boardPin == "P0" || boardPin == "P1" || boardPin == "P0" || boardPin == "GND" || boardPin == "+3v3" ) {
//HACK
wireEls = this.drawWireWithCrocs(startLoc, endLoc, color);
} else {
wireEls = this.drawWireWithCrocs(startLoc, endLoc, color, true);
}
} else {
wireEls = this.drawWire(startLoc, endLoc, color);
}
return wireEls;
}
}
}