Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
46f7831e7c | |||
dda29a5cb6 | |||
6e4f4595a2 | |||
cdbe1e513b | |||
ae15c9a656 | |||
d993ff3a9d | |||
13785a2438 | |||
4dfb77fcd7 | |||
70deffb665 | |||
41a148de28 | |||
886e071d7f | |||
8a58d664c3 | |||
71d1155f21 | |||
167c1d8fce | |||
e59ae37954 | |||
72a621ec8b | |||
d6ff930333 | |||
54a7ac81ea | |||
e5d985dbf1 | |||
9db91d89d6 | |||
1fa9bf12d5 | |||
61bab257eb | |||
2e90b351da | |||
801bd6c7a0 | |||
777ba40899 | |||
0d11c16ecf | |||
9c1628b977 | |||
953b362b34 | |||
8b40850a94 | |||
fc3a02cc41 | |||
80f2ff6757 | |||
6f790d167c | |||
ac7502074a | |||
50473255a8 | |||
910772d54e | |||
33f12f9ecc | |||
01fa4ef53a | |||
3858b0a0a0 | |||
da30afb121 | |||
086bcf372f | |||
236e7337e2 | |||
4cf223271f | |||
10180f4729 | |||
618dd33221 | |||
7356e5e52e | |||
a59d148eb5 | |||
34ce687bbd | |||
5e241ed566 | |||
dcb2b21e66 | |||
95669e174a | |||
aca7d6113d | |||
ef4b06a087 | |||
afa69c23c4 | |||
8229e71d0a | |||
93770e5821 | |||
cb8e28beb0 | |||
8c7e6055ff | |||
8450db55ac | |||
c63e2c85f1 | |||
d8fc11a688 | |||
847a29848d | |||
39f4372ee5 | |||
a8bbbea8ed | |||
91a8d05e45 | |||
3ecedd3a32 | |||
b6390d77dd | |||
e8f9e0f023 | |||
2bacb58fdd | |||
af8babfea2 | |||
1ee6274100 | |||
7448db5b98 | |||
dfe270d259 |
137
clients/macuploader/.gitignore
vendored
Normal 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
|
BIN
clients/macuploader/Graphics/appicon.sketch
Normal file
BIN
clients/macuploader/Graphics/export.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
clients/macuploader/Graphics/menubar.sketch
Normal file
307
clients/macuploader/Microbit Uploader.xcodeproj/project.pbxproj
Normal 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 */;
|
||||||
|
}
|
7
clients/macuploader/Microbit Uploader.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:Microbit Uploader.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
6
clients/macuploader/Microbit Uploader/AppDelegate.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
124
clients/macuploader/Microbit Uploader/AppDelegate.m
Normal 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
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 744 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 435 KiB |
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
15
clients/macuploader/Microbit Uploader/Assets.xcassets/menubar.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "menubar.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
BIN
clients/macuploader/Microbit Uploader/Assets.xcassets/menubar.imageset/menubar.pdf
vendored
Normal file
681
clients/macuploader/Microbit Uploader/Base.lproj/MainMenu.xib
Normal 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>
|
24
clients/macuploader/Microbit Uploader/DirectoryWatcher.h
Normal 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
|
73
clients/macuploader/Microbit Uploader/DirectoryWatcher.m
Normal 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];
|
||||||
|
}
|
36
clients/macuploader/Microbit Uploader/Info.plist
Normal 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>
|
21
clients/macuploader/Microbit Uploader/Uploader.h
Normal 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
|
74
clients/macuploader/Microbit Uploader/Uploader.m
Normal 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:©Error]) {
|
||||||
|
[self.delegate uploader:self failedToTransferFile:file toMicroBit:path.lastPathComponent];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[self.delegate uploader:self transferredFile:file toMicroBit:path.lastPathComponent];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
5
clients/macuploader/Microbit Uploader/main.m
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
int main(int argc, const char * argv[]) {
|
||||||
|
return NSApplicationMain(argc, argv);
|
||||||
|
}
|
40
clients/macuploader/README.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# micro:bit uploader for OS X
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
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
|
BIN
clients/macuploader/Release/Microbit Uploader v1.0.zip
Normal file
@ -19,10 +19,3 @@
|
|||||||
<link rel="shortcut icon" href="@appLogo@">
|
<link rel="shortcut icon" href="@appLogo@">
|
||||||
<meta name="theme-color" content="@accentColor@">
|
<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
@ -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
@ -0,0 +1,13 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
var appInsights=window.appInsights||function(config){
|
||||||
|
function r(config){t[config]=function(){var i=arguments;t.queue.push(function(){t[config].apply(t,i)})}}var t={config:config},u=document,e=window,o="script",s=u.createElement(o),i,f;for(s.src=config.url||"//az416426.vo.msecnd.net/scripts/a/ai.0.js",u.getElementsByTagName(o)[0].parentNode.appendChild(s),t.cookie=u.cookie,t.queue=[],i=["Event","Exception","Metric","PageView","Trace"];i.length;)r("track"+i.pop());return r("setAuthenticatedUserContext"),r("clearAuthenticatedUserContext"),config.disableExceptionTracking||(i="onerror",r("_"+i),f=e[i],e[i]=function(config,r,u,e,o){var s=f&&f(config,r,u,e,o);return s!==!0&&t["_"+i](config,r,u,e,o),s}),t
|
||||||
|
}({
|
||||||
|
instrumentationKey:"e9ae05ca-350b-427a-9775-3ba3f6efabce"
|
||||||
|
});window.appInsights=appInsights;
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
|
||||||
|
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
|
||||||
|
|
||||||
|
mixpanel.init("762fef19c053a0ea4cec43d2fecae76e");
|
||||||
|
</script>
|
@ -1,3 +1,50 @@
|
|||||||
|

|
||||||
|
|
||||||
|
# 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
|
```sim
|
||||||
basic.forever(() => {
|
basic.forever(() => {
|
||||||
basic.showString("Hi!");
|
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
|
## 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,
|
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.
|
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.
|
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!
|
The code for the micro:bit is [open source](/open-source) on GitHub. Contributors are welcome!
|
||||||
|
101
docs/browsers.md
Normal 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Mac
|
||||||
|
|
||||||
|
* Click on the Apple icon in the top left
|
||||||
|
* Click on 'About this Mac'
|
||||||
|
* This window will be displayed:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Internet Explorer
|
||||||
|
|
||||||
|
* Click on the Settings icon in the top right
|
||||||
|
* Click 'About Internet Explorer'
|
||||||
|
* This window will be displayed:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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'
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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'
|
||||||
|
|
||||||
|

|
6
docs/browsers/linux.md
Normal 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
@ -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
@ -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.
|
@ -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.
|
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.
|
Click the **folder icon** next to the filename to open it in Windows Explorer.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Drag and drop the `.hex` file from the download folder onto the `MICROBIT` drive.
|
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,
|
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.
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
You can’t drag and drop more than one hex file at once onto your micro:bit. If
|
You can’t drag and drop more than one hex file at once onto your micro:bit. If
|
||||||
|
@ -58,7 +58,9 @@ After running this simulation several seconds by moving the micro:bit side to si
|
|||||||

|

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

|

|
||||||
|
|
||||||
@ -93,4 +95,4 @@ NEXT: The Watch
|
|||||||
|
|
||||||
```package
|
```package
|
||||||
microbit-radio
|
microbit-radio
|
||||||
```
|
```
|
||||||
|
29
docs/raspberry-pi.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Raspberry Pi and Raspbian
|
||||||
|
|
||||||
|
It is possible to run the web editor or [command line interface](/cli) from Raspbian on Raspberry Pi 2 or 3.
|
||||||
|
|
||||||
|
## Web editor
|
||||||
|
|
||||||
|
The web editor requires to install IceWeasel (Firefox) as the built-in browser cannot handle it.
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get install iceweasel
|
||||||
|
```
|
||||||
|
|
||||||
|
Once installed simply navigate to https://codethemicrobit.com or type
|
||||||
|
|
||||||
|
```
|
||||||
|
firefox https://codethemicrobit.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command line
|
||||||
|
|
||||||
|
The PXT command line also works on Raspbian and allows to run a local server and/or edit programs from any text editor.
|
||||||
|
|
||||||
|
* Node.JS 6.0 needs installed
|
||||||
|
|
||||||
|
To install all the tools,
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -s https://raw.githubusercontent.com/Microsoft/pxt-rpi/master/install.sh | sh -
|
||||||
|
```
|
BIN
docs/static/configurations/chrome-version.png
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/static/configurations/edge-version.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
docs/static/configurations/firefox-version.png
vendored
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
docs/static/configurations/ie-version.png
vendored
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
docs/static/configurations/osx-version.png
vendored
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
docs/static/configurations/safari-version.png
vendored
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
docs/static/configurations/windows-version.png
vendored
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
docs/static/mb/device/pano.jpg
vendored
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
docs/static/mb/device/usb-generic.jpg
vendored
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
docs/static/mb/device/usb-mac.jpg
vendored
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
docs/static/mb/device/usb-osx-chrome.png
vendored
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 30 KiB |
BIN
docs/static/mb/device/usb-osx-firefox-1.jpg
vendored
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
docs/static/mb/device/usb-osx-firefox-1.png
vendored
Before Width: | Height: | Size: 102 KiB |
BIN
docs/static/mb/device/usb-windows-chrome.png
vendored
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 54 KiB |
BIN
docs/static/mb/device/usb-windows-edge-1.png
vendored
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 24 KiB |
BIN
docs/static/mb/device/usb-windows-edge-2.png
vendored
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 18 KiB |
BIN
docs/static/mb/device/usb-windows-firefox-1.png
vendored
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
docs/static/mb/device/usb-windows-firefox-2.jpg
vendored
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/static/mb/device/usb-windows-firefox-2.png
vendored
Before Width: | Height: | Size: 120 KiB |
BIN
docs/static/mb/device/usb-windows-ie11-1.png
vendored
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 10 KiB |
BIN
docs/static/mb/device/usb-windows-ie11-2.png
vendored
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 10 KiB |
BIN
docs/static/mb/device/usb-windows-sendto.jpg
vendored
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 50 KiB |
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"bluetooth": "Support for additional Bluetooth services.",
|
|
||||||
"bluetooth.onBluetoothConnected": "Register code to run when the micro:bit is connected to over Bluetooth",
|
"bluetooth.onBluetoothConnected": "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.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",
|
"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.startLEDService": "Starts the Bluetooth LED service",
|
||||||
"bluetooth.startMagnetometerService": "Starts the Bluetooth magnetometer service",
|
"bluetooth.startMagnetometerService": "Starts the Bluetooth magnetometer service",
|
||||||
"bluetooth.startTemperatureService": "Starts the Bluetooth temperature 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.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."
|
"bluetooth.uartWrite": "Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device."
|
||||||
}
|
}
|
@ -7,6 +7,7 @@
|
|||||||
"bluetooth.startLEDService|block": "bluetooth led service",
|
"bluetooth.startLEDService|block": "bluetooth led service",
|
||||||
"bluetooth.startMagnetometerService|block": "bluetooth magnetometer service",
|
"bluetooth.startMagnetometerService|block": "bluetooth magnetometer service",
|
||||||
"bluetooth.startTemperatureService|block": "bluetooth temperature 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.uartRead|block": "bluetooth uart read %del=bluetooth_uart_delimiter_conv",
|
||||||
"bluetooth.uartWrite|block": "bluetooth uart write %data",
|
"bluetooth.uartWrite|block": "bluetooth uart write %data",
|
||||||
"bluetooth|block": "bluetooth"
|
"bluetooth|block": "bluetooth"
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
"control": "Runtime and event utilities.",
|
"control": "Runtime and event utilities.",
|
||||||
"control.inBackground": "Schedules code that run in the background.",
|
"control.inBackground": "Schedules code that run in the background.",
|
||||||
"control.reset": "Resets the BBC micro:bit.",
|
"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": "A single-LED sprite game engine",
|
||||||
"game.addScore": "Adds points to the current score",
|
"game.addScore": "Adds points to the current score",
|
||||||
"game.addScore|param|points": "amount of points to change, eg: 1",
|
"game.addScore|param|points": "amount of points to change, eg: 1",
|
||||||
@ -52,9 +54,12 @@
|
|||||||
"input.onLogoDown|param|body": "TODO",
|
"input.onLogoDown|param|body": "TODO",
|
||||||
"input.onLogoUp": "Attaches code to run when the logo is oriented upwards and the board is vertical.",
|
"input.onLogoUp": "Attaches code to run when the logo is oriented upwards and the board is vertical.",
|
||||||
"input.onLogoUp|param|body": "TODO",
|
"input.onLogoUp|param|body": "TODO",
|
||||||
"input.onPinPressed": "Do something when a pin(``P0``, ``P1`` or both ``P2``) is pressed.",
|
"input.onPinPressed": "Do something when a pin is pressed.",
|
||||||
"input.onPinPressed|param|body": "TODO",
|
"input.onPinPressed|param|body": "the code to run when the pin is pressed",
|
||||||
"input.onPinPressed|param|name": "TODO",
|
"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": "Attaches code to run when the screen is facing down.",
|
||||||
"input.onScreenDown|param|body": "TODO",
|
"input.onScreenDown|param|body": "TODO",
|
||||||
"input.onScreenUp": "Attaches code to run when the screen is facing up.",
|
"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": "Configures the pull of this pin.",
|
||||||
"pins.setPull|param|name": "pin to set the pull mode on",
|
"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.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": "Reading and writing data over a serial connection.",
|
||||||
"serial.readLine": "Reads a line of text from the serial port.",
|
"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.",
|
"serial.redirect": "Dynamically configuring the serial instance to use pins other than USBTX and USBRX.",
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"basic|block": "basic",
|
"basic|block": "basic",
|
||||||
"control.inBackground|block": "run in background",
|
"control.inBackground|block": "run in background",
|
||||||
"control.reset|block": "reset",
|
"control.reset|block": "reset",
|
||||||
|
"control.waitMicros|block": "wait (µs)%micros",
|
||||||
"control|block": "control",
|
"control|block": "control",
|
||||||
"game.addScore|block": "change score by|%points",
|
"game.addScore|block": "change score by|%points",
|
||||||
"game.gameOver|block": "game over",
|
"game.gameOver|block": "game over",
|
||||||
@ -28,7 +29,8 @@
|
|||||||
"input.magneticForce|block": "magnetic force (µT)|%NAME",
|
"input.magneticForce|block": "magnetic force (µT)|%NAME",
|
||||||
"input.onButtonPressed|block": "on button|%NAME|pressed",
|
"input.onButtonPressed|block": "on button|%NAME|pressed",
|
||||||
"input.onGesture|block": "on |%NAME",
|
"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.pinIsPressed|block": "pin %NAME|is pressed",
|
||||||
"input.rotation|block": "rotation (°)|%NAME",
|
"input.rotation|block": "rotation (°)|%NAME",
|
||||||
"input.runningTime|block": "running time (ms)",
|
"input.runningTime|block": "running time (ms)",
|
||||||
@ -41,6 +43,7 @@
|
|||||||
"led.point|block": "point|x %x|y %y",
|
"led.point|block": "point|x %x|y %y",
|
||||||
"led.setBrightness|block": "set brightness %value",
|
"led.setBrightness|block": "set brightness %value",
|
||||||
"led.stopAnimation|block": "stop animation",
|
"led.stopAnimation|block": "stop animation",
|
||||||
|
"led.toggle|block": "toggle|x %x|y %y",
|
||||||
"led.unplot|block": "unplot|x %x|y %y",
|
"led.unplot|block": "unplot|x %x|y %y",
|
||||||
"led|block": "led",
|
"led|block": "led",
|
||||||
"music.beat|block": "%fraction|beat",
|
"music.beat|block": "%fraction|beat",
|
||||||
@ -65,6 +68,7 @@
|
|||||||
"pins.servoSetPulse|block": "servo set pulse|pin %value|to (µs) %micros",
|
"pins.servoSetPulse|block": "servo set pulse|pin %value|to (µs) %micros",
|
||||||
"pins.servoWritePin|block": "servo write|pin %name|to %value",
|
"pins.servoWritePin|block": "servo write|pin %name|to %value",
|
||||||
"pins.setPull|block": "set pull|pin %pin|to %pull",
|
"pins.setPull|block": "set pull|pin %pin|to %pull",
|
||||||
|
"pins.spiWrite|block": "spi write %value",
|
||||||
"pins|block": "pins",
|
"pins|block": "pins",
|
||||||
"serial.readLine|block": "serial read line",
|
"serial.readLine|block": "serial read line",
|
||||||
"serial.redirect|block": "serial redirect to|TX %tx|RX %rx|at baud rate %rate",
|
"serial.redirect|block": "serial redirect to|TX %tx|RX %rx|at baud rate %rate",
|
||||||
|
@ -42,6 +42,5 @@ namespace control {
|
|||||||
* Display warning in the simulator.
|
* Display warning in the simulator.
|
||||||
*/
|
*/
|
||||||
//% shim=pxtrt::runtimeWarning
|
//% shim=pxtrt::runtimeWarning
|
||||||
export function runtimeWarning(message: string) {
|
export function runtimeWarning(message: string) { }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,7 @@ namespace Array_ {
|
|||||||
int removeElement(RefCollection *c, uint32_t x) { return c->removeElement(x); }
|
int removeElement(RefCollection *c, uint32_t x) { return c->removeElement(x); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Import some stuff directly
|
// Import some stuff directly
|
||||||
namespace pxt {
|
namespace pxt {
|
||||||
//%
|
//%
|
||||||
@ -181,10 +182,12 @@ namespace pxt {
|
|||||||
//%
|
//%
|
||||||
Action mkAction(int reflen, int totallen, int startptr);
|
Action mkAction(int reflen, int totallen, int startptr);
|
||||||
//%
|
//%
|
||||||
RefRecord* mkRecord(int reflen, int totallen);
|
|
||||||
//%
|
|
||||||
RefRecord* mkClassInstance(int offset);
|
RefRecord* mkClassInstance(int offset);
|
||||||
//%
|
//%
|
||||||
|
void RefRecord_destroy(RefRecord *r);
|
||||||
|
//%
|
||||||
|
void RefRecord_print(RefRecord *r);
|
||||||
|
//%
|
||||||
void debugMemLeaks();
|
void debugMemLeaks();
|
||||||
//%
|
//%
|
||||||
int incr(uint32_t e);
|
int incr(uint32_t e);
|
||||||
|
@ -1,78 +1,126 @@
|
|||||||
{
|
{
|
||||||
"ledmatrix": {
|
"buttonpair": {
|
||||||
"visual": "ledmatrix",
|
"simulationBehavior": "buttonpair",
|
||||||
"breadboardColumnsNeeded": 8,
|
"visual": {
|
||||||
"breadboardStartRow": "h",
|
"builtIn": "buttonpair",
|
||||||
"pinAllocation": {
|
"width": 75,
|
||||||
"type": "auto",
|
"height": 45,
|
||||||
"gpioPinsNeeded": [5, 5]
|
"pinDistance": 15,
|
||||||
|
"pinLocations": [
|
||||||
|
{"x": 0, "y": 0},
|
||||||
|
{"x": 30, "y": 45},
|
||||||
|
{"x": 45, "y": 0},
|
||||||
|
{"x": 75, "y": 45}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"assemblyStep": 0,
|
"numberOfPins": 4,
|
||||||
"wires": [
|
"pinDefinitions": [
|
||||||
{"start": ["breadboard", "j", 0], "end": ["GPIO", 5], "color": "purple", "assemblyStep": 1},
|
{"target": "P14", "style": "male", "orientation": "-Z"},
|
||||||
{"start": ["breadboard", "j", 1], "end": ["GPIO", 6], "color": "purple", "assemblyStep": 1},
|
{"target": "ground", "style": "male", "orientation": "-Z"},
|
||||||
{"start": ["breadboard", "j", 2], "end": ["GPIO", 7], "color": "purple", "assemblyStep": 1},
|
{"target": "P15", "style": "male", "orientation": "-Z"},
|
||||||
{"start": ["breadboard", "j", 3], "end": ["GPIO", 8], "color": "purple", "assemblyStep": 1},
|
{"target": "ground", "style": "male", "orientation": "-Z"}
|
||||||
{"start": ["breadboard", "a", 7], "end": ["GPIO", 9], "color": "purple", "assemblyStep": 1},
|
],
|
||||||
{"start": ["breadboard", "a", 0], "end": ["GPIO", 0], "color": "green", "assemblyStep": 2},
|
"instantiation": {
|
||||||
{"start": ["breadboard", "a", 1], "end": ["GPIO", 1], "color": "green", "assemblyStep": 2},
|
"kind": "singleton"
|
||||||
{"start": ["breadboard", "a", 2], "end": ["GPIO", 2], "color": "green", "assemblyStep": 2},
|
|
||||||
{"start": ["breadboard", "a", 3], "end": ["GPIO", 3], "color": "green", "assemblyStep": 2},
|
|
||||||
{"start": ["breadboard", "j", 4], "end": ["GPIO", 4], "color": "green", "assemblyStep": 2}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"buttonpair": {
|
|
||||||
"visual": "buttonpair",
|
|
||||||
"breadboardColumnsNeeded": 6,
|
|
||||||
"breadboardStartRow": "f",
|
|
||||||
"pinAllocation": {
|
|
||||||
"type": "predefined",
|
|
||||||
"pins": ["P13", "P12"]
|
|
||||||
},
|
},
|
||||||
"assemblyStep": 0,
|
"assembly": [
|
||||||
"wires": [
|
{"part": true},
|
||||||
{"start": ["breadboard", "j", 0], "end": ["GPIO", 0], "color": "yellow", "assemblyStep": 1},
|
{"pinIndices": [0, 1]},
|
||||||
{"start": ["breadboard", "a", 2], "end": "ground", "color": "blue", "assemblyStep": 1},
|
{"pinIndices": [2, 3]}
|
||||||
{"start": ["breadboard", "j", 3], "end": ["GPIO", 1], "color": "orange", "assemblyStep": 2},
|
|
||||||
{"start": ["breadboard", "a", 5], "end": "ground", "color": "blue", "assemblyStep": 2}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"neopixel": {
|
"neopixel": {
|
||||||
"visual": "neopixel",
|
"simulationBehavior": "neopixel",
|
||||||
"breadboardColumnsNeeded": 5,
|
"visual": {
|
||||||
"breadboardStartRow": "h",
|
"builtIn": "neopixel",
|
||||||
"pinAllocation": {
|
"width": 58,
|
||||||
"type": "factoryfunction",
|
"height": 113,
|
||||||
"functionName": "neopixel.create",
|
"pinDistance": 9,
|
||||||
"pinArgPositions": [0],
|
"pinLocations": [
|
||||||
"otherArgPositions": [1]
|
{"x": 10, "y": 0},
|
||||||
|
{"x": 19, "y": 0},
|
||||||
|
{"x": 28, "y": 0}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"assemblyStep": 0,
|
"numberOfPins": 3,
|
||||||
"wires": [
|
"pinDefinitions": [
|
||||||
{"start": ["breadboard", "j", 1], "end": "ground", "color": "blue", "assemblyStep": 1},
|
{"target": {"pinInstantiationIdx": 0}, "style": "solder", "orientation": "+Z"},
|
||||||
{"start": ["breadboard", "j", 2], "end": "threeVolt", "color": "red", "assemblyStep": 2},
|
{"target": "threeVolt", "style": "solder", "orientation": "+Z"},
|
||||||
{"start": ["breadboard", "j", 3], "end": ["GPIO", 0], "color": "green", "assemblyStep": 2}
|
{"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": {
|
"speaker": {
|
||||||
|
"numberOfPins": 2,
|
||||||
"visual": {
|
"visual": {
|
||||||
"image": "/parts/speaker.svg",
|
"image": "/parts/speaker.svg",
|
||||||
"width": 500,
|
"width": 500,
|
||||||
"height": 500,
|
"height": 500,
|
||||||
"firstPin": [180, 135],
|
"pinDistance": 70,
|
||||||
"pinDist": 70,
|
"pinLocations": [
|
||||||
"extraColumnOffset": 1
|
{"x": 180, "y": 135},
|
||||||
|
{"x": 320, "y": 135}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"breadboardColumnsNeeded": 5,
|
"pinDefinitions": [
|
||||||
"breadboardStartRow": "f",
|
{"target": "P0", "style": "male", "orientation": "-Z"},
|
||||||
"pinAllocation": {
|
{"target": "ground", "style": "male", "orientation": "-Z"}
|
||||||
"type": "auto",
|
],
|
||||||
"gpioPinsNeeded": 1
|
"instantiation": {"kind": "singleton"},
|
||||||
},
|
"assembly": [
|
||||||
"assemblyStep": 0,
|
{"part": true, "pinIndices": [0]},
|
||||||
"wires": [
|
{"pinIndices": [1]}
|
||||||
{"start": ["breadboard", "j", 1], "end": ["GPIO", 0], "color": "#ff80fa", "assemblyStep": 1},
|
|
||||||
{"start": ["breadboard", "j", 3], "end": "ground", "color": "blue", "assemblyStep": 1}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pxt-microbit",
|
"name": "pxt-microbit",
|
||||||
"version": "0.3.80",
|
"version": "0.4.7",
|
||||||
"description": "micro:bit target for PXT",
|
"description": "micro:bit target for PXT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"JavaScript",
|
"JavaScript",
|
||||||
@ -29,6 +29,6 @@
|
|||||||
"typescript": "^1.8.7"
|
"typescript": "^1.8.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pxt-core": "0.3.93"
|
"pxt-core": "0.4.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
187
pxtarget.json
@ -75,7 +75,9 @@
|
|||||||
},
|
},
|
||||||
"simulator": {
|
"simulator": {
|
||||||
"autoRun": true,
|
"autoRun": true,
|
||||||
|
"streams": true,
|
||||||
"aspectRatio": 1.22,
|
"aspectRatio": 1.22,
|
||||||
|
"parts": true,
|
||||||
"partsAspectRatio": 0.69,
|
"partsAspectRatio": 0.69,
|
||||||
"builtinParts": {
|
"builtinParts": {
|
||||||
"accelerometer": true,
|
"accelerometer": true,
|
||||||
@ -85,13 +87,102 @@
|
|||||||
"bluetooth": true,
|
"bluetooth": true,
|
||||||
"thermometer": true,
|
"thermometer": true,
|
||||||
"compass": 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": {
|
"compileService": {
|
||||||
"yottaTarget": "bbc-microbit-classic-gcc",
|
"yottaTarget": "bbc-microbit-classic-gcc",
|
||||||
"yottaCorePackage": "pxt-microbit-core",
|
"yottaCorePackage": "pxt-microbit-core",
|
||||||
"githubCorePackage": "microsoft/pxt-microbit-core",
|
"githubCorePackage": "microsoft/pxt-microbit-core",
|
||||||
"gittag": "v0.4.4",
|
"gittag": "v0.5.0",
|
||||||
"serviceId": "ws"
|
"serviceId": "ws"
|
||||||
},
|
},
|
||||||
"serial": {
|
"serial": {
|
||||||
@ -114,6 +205,33 @@
|
|||||||
"privacyUrl": "https://go.microsoft.com/fwlink/?LinkId=521839",
|
"privacyUrl": "https://go.microsoft.com/fwlink/?LinkId=521839",
|
||||||
"termsOfUseUrl": "https://go.microsoft.com/fwlink/?LinkID=206977",
|
"termsOfUseUrl": "https://go.microsoft.com/fwlink/?LinkID=206977",
|
||||||
"githubUrl": "https://github.com/Microsoft/pxt-microbit",
|
"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",
|
"boardName": "BBC micro:bit",
|
||||||
"docMenu": [
|
"docMenu": [
|
||||||
{
|
{
|
||||||
@ -141,10 +259,73 @@
|
|||||||
"path": "/device"
|
"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": {
|
"analytics": {
|
||||||
"userVoiceApiKey": "WEkkIGaj1WtJnSUF59iwaA",
|
"userVoiceApiKey": "WEkkIGaj1WtJnSUF59iwaA",
|
||||||
"userVoiceForumId": 402381
|
"userVoiceForumId": 402381
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
556
sim/allocator.ts
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
132
sim/dalboard.ts
@ -1,12 +1,7 @@
|
|||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||||
|
|
||||||
namespace pxsim {
|
namespace pxsim {
|
||||||
export class DalBoard extends BaseBoard {
|
export class DalBoard extends CoreBoard {
|
||||||
id: string;
|
|
||||||
|
|
||||||
// the bus
|
|
||||||
bus: EventBus;
|
|
||||||
|
|
||||||
// state & update logic for component services
|
// state & update logic for component services
|
||||||
ledMatrixState: LedMatrixState;
|
ledMatrixState: LedMatrixState;
|
||||||
edgeConnectorState: EdgeConnectorState;
|
edgeConnectorState: EdgeConnectorState;
|
||||||
@ -19,31 +14,58 @@ namespace pxsim {
|
|||||||
radioState: RadioState;
|
radioState: RadioState;
|
||||||
neopixelState: NeoPixelState;
|
neopixelState: NeoPixelState;
|
||||||
|
|
||||||
// updates
|
|
||||||
updateSubscribers: (() => void)[];
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.id = "b" + Math_.random(2147483647);
|
|
||||||
this.bus = new EventBus(runtime);
|
|
||||||
|
|
||||||
// components
|
// components
|
||||||
this.ledMatrixState = new LedMatrixState(runtime);
|
this.builtinParts["ledmatrix"] = this.ledMatrixState = new LedMatrixState(runtime);
|
||||||
this.buttonPairState = new ButtonPairState();
|
this.builtinParts["buttonpair"] = this.buttonPairState = new ButtonPairState({
|
||||||
this.edgeConnectorState = new EdgeConnectorState();
|
ID_BUTTON_A: DAL.MICROBIT_ID_BUTTON_A,
|
||||||
this.radioState = new RadioState(runtime);
|
ID_BUTTON_B: DAL.MICROBIT_ID_BUTTON_B,
|
||||||
this.accelerometerState = new AccelerometerState(runtime);
|
ID_BUTTON_AB: DAL.MICROBIT_ID_BUTTON_AB,
|
||||||
this.serialState = new SerialState();
|
BUTTON_EVT_UP: DAL.MICROBIT_BUTTON_EVT_UP,
|
||||||
this.thermometerState = new ThermometerState();
|
BUTTON_EVT_CLICK: DAL.MICROBIT_BUTTON_EVT_CLICK
|
||||||
this.lightSensorState = new LightSensorState();
|
});
|
||||||
this.compassState = new CompassState();
|
this.builtinParts["edgeconnector"] = this.edgeConnectorState = new EdgeConnectorState({
|
||||||
this.neopixelState = new NeoPixelState();
|
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.builtinVisuals["buttonpair"] = () => new visuals.ButtonPairView();
|
||||||
this.updateSubscribers = []
|
this.builtinVisuals["ledmatrix"] = () => new visuals.LedMatrixView();
|
||||||
this.updateView = () => {
|
this.builtinVisuals["neopixel"] = () => new visuals.NeoPixelView();
|
||||||
this.updateSubscribers.forEach(sub => sub());
|
|
||||||
}
|
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) {
|
receiveMessage(msg: SimulatorMessage) {
|
||||||
@ -65,31 +87,26 @@ namespace pxsim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kill() {
|
|
||||||
super.kill();
|
|
||||||
AudioContextManager.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
initAsync(msg: SimulatorRunMessage): Promise<void> {
|
||||||
super.initAsync(msg);
|
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;
|
const opts : visuals.BoardHostOpts = {
|
||||||
let cmpDefs = msg.partDefinitions || {}; //TODO: read from pxt.json/pxttarget.json
|
|
||||||
let fnArgs = msg.fnArgs;
|
|
||||||
|
|
||||||
let viewHost = new visuals.BoardHost({
|
|
||||||
state: this,
|
state: this,
|
||||||
boardDef: boardDef,
|
boardDef: boardDef,
|
||||||
cmpsList: cmpsList,
|
partsList: cmpsList,
|
||||||
cmpDefs: cmpDefs,
|
partDefs: cmpDefs,
|
||||||
fnArgs: fnArgs,
|
fnArgs: fnArgs,
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
maxHeight: "100%",
|
maxHeight: "100%",
|
||||||
});
|
};
|
||||||
|
const viewHost = new visuals.BoardHost(pxsim.visuals.mkBoardView(opts), opts);
|
||||||
|
|
||||||
document.body.innerHTML = ""; // clear children
|
document.body.innerHTML = ""; // clear children
|
||||||
document.body.appendChild(viewHost.getView());
|
document.body.appendChild(viewHost.getView());
|
||||||
@ -97,4 +114,37 @@ namespace pxsim {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initRuntimeWithDalBoard() {
|
||||||
|
U.assert(!runtime.board);
|
||||||
|
let b = new DalBoard();
|
||||||
|
runtime.board = b;
|
||||||
|
runtime.postError = (e) => {
|
||||||
|
led.setBrightness(255);
|
||||||
|
let img = board().ledMatrixState.image;
|
||||||
|
img.clear();
|
||||||
|
img.set(0, 4, 255);
|
||||||
|
img.set(1, 3, 255);
|
||||||
|
img.set(2, 3, 255);
|
||||||
|
img.set(3, 3, 255);
|
||||||
|
img.set(4, 4, 255);
|
||||||
|
img.set(0, 0, 255);
|
||||||
|
img.set(1, 0, 255);
|
||||||
|
img.set(0, 1, 255);
|
||||||
|
img.set(1, 1, 255);
|
||||||
|
img.set(3, 0, 255);
|
||||||
|
img.set(4, 0, 255);
|
||||||
|
img.set(3, 1, 255);
|
||||||
|
img.set(4, 1, 255);
|
||||||
|
runtime.updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pxsim.initCurrentRuntime) {
|
||||||
|
pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function board() {
|
||||||
|
return runtime.board as DalBoard;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,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;
|
|
||||||
}
|
|
@ -1,9 +1,6 @@
|
|||||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.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/pxtsim.d.ts"/>
|
||||||
/// <reference path="../../node_modules/pxt-core/built/pxtrunner.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"/>
|
|
||||||
|
|
||||||
//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
|
//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
|
||||||
if (!(<any>window).pxt) (<any>window).pxt = {};
|
if (!(<any>window).pxt) (<any>window).pxt = {};
|
||||||
@ -20,7 +17,7 @@ namespace pxsim.instructions {
|
|||||||
const LBL_LEFT_PAD = 5;
|
const LBL_LEFT_PAD = 5;
|
||||||
const REQ_WIRE_HEIGHT = 45;
|
const REQ_WIRE_HEIGHT = 45;
|
||||||
const REQ_CMP_HEIGHT = 55;
|
const REQ_CMP_HEIGHT = 55;
|
||||||
const REQ_CMP_SCALE = 0.5 * 4;
|
const REQ_CMP_SCALE = 0.5 * 3;
|
||||||
type Orientation = "landscape" | "portrait";
|
type Orientation = "landscape" | "portrait";
|
||||||
const ORIENTATION: Orientation = "portrait";
|
const ORIENTATION: Orientation = "portrait";
|
||||||
const PPI = 96.0;
|
const PPI = 96.0;
|
||||||
@ -59,7 +56,7 @@ namespace pxsim.instructions {
|
|||||||
border-color: ${BORDER_COLOR};
|
border-color: ${BORDER_COLOR};
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: ${BORDER_RADIUS}px;
|
border-radius: ${BORDER_RADIUS}px;
|
||||||
display: block;
|
display: inline-block;
|
||||||
width: ${PANEL_WIDTH}px;
|
width: ${PANEL_WIDTH}px;
|
||||||
height: ${PANEL_HEIGHT}px;
|
height: ${PANEL_HEIGHT}px;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -284,18 +281,19 @@ namespace pxsim.instructions {
|
|||||||
div.appendChild(svgEl);
|
div.appendChild(svgEl);
|
||||||
return div;
|
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;
|
let el: visuals.SVGElAndSize;
|
||||||
if (cmp == "wire") {
|
if (cmp == "wire") {
|
||||||
//TODO: support non-croc wire parts
|
|
||||||
el = visuals.mkWirePart([0, 0], opts.wireClr || "red", opts.crocClips);
|
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 {
|
} else {
|
||||||
let partVis = <PartVisualDefinition>cmp;
|
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);
|
return wrapSvg(el, opts);
|
||||||
}
|
}
|
||||||
@ -305,40 +303,33 @@ namespace pxsim.instructions {
|
|||||||
fnArgs: any,
|
fnArgs: any,
|
||||||
allAlloc: AllocatorResult,
|
allAlloc: AllocatorResult,
|
||||||
stepToWires: WireInst[][],
|
stepToWires: WireInst[][],
|
||||||
stepToCmps: CmpInst[][]
|
stepToCmps: PartInst[][]
|
||||||
allWires: WireInst[],
|
allWires: WireInst[],
|
||||||
allCmps: CmpInst[],
|
allCmps: PartInst[],
|
||||||
lastStep: number,
|
lastStep: number,
|
||||||
colorToWires: Map<WireInst[]>,
|
colorToWires: Map<WireInst[]>,
|
||||||
allWireColors: string[],
|
allWireColors: string[],
|
||||||
};
|
};
|
||||||
function mkBoardProps(allocOpts: AllocatorOpts): BoardProps {
|
function mkBoardProps(allocOpts: AllocatorOpts): BoardProps {
|
||||||
let allocRes = allocateDefinitions(allocOpts);
|
let allocRes = allocateDefinitions(allocOpts);
|
||||||
let {powerWires, components} = allocRes;
|
|
||||||
let stepToWires: WireInst[][] = [];
|
let stepToWires: WireInst[][] = [];
|
||||||
let stepToCmps: CmpInst[][] = [];
|
let stepToCmps: PartInst[][] = [];
|
||||||
powerWires.forEach(w => {
|
let stepOffset = 0;
|
||||||
let step = w.assemblyStep + 1;
|
allocRes.partsAndWires.forEach(cAndWs => {
|
||||||
(stepToWires[step] || (stepToWires[step] = [])).push(w)
|
let part = cAndWs.part;
|
||||||
});
|
let wires = cAndWs.wires;
|
||||||
let getMaxStep = (ns: { assemblyStep: number }[]) => ns.reduce((m, n) => Math.max(m, n.assemblyStep), 0);
|
cAndWs.assembly.forEach((step, idx) => {
|
||||||
let stepOffset = powerWires.length > 0 ? getMaxStep(powerWires) + 2 : 1;
|
if (step.part && part)
|
||||||
components.forEach(cAndWs => {
|
stepToCmps[stepOffset + idx] = [part]
|
||||||
let {component, wires} = cAndWs;
|
if (step.wireIndices && step.wireIndices.length > 0 && wires)
|
||||||
let cStep = component.assemblyStep + stepOffset;
|
stepToWires[stepOffset + idx] = step.wireIndices.map(i => wires[i])
|
||||||
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);
|
|
||||||
})
|
})
|
||||||
stepOffset = Math.max(cStep, wSteps.reduce((m, n) => Math.max(m, n), 0)) + 1;
|
stepOffset += cAndWs.assembly.length;
|
||||||
});
|
});
|
||||||
let lastStep = stepOffset - 1;
|
let numSteps = stepOffset;
|
||||||
let allCmps = components.map(p => p.component);
|
let lastStep = numSteps - 1;
|
||||||
let allWires = powerWires.concat(components.map(p => p.wires).reduce((p, n) => p.concat(n), []));
|
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 colorToWires: Map<WireInst[]> = {}
|
||||||
let allWireColors: string[] = [];
|
let allWireColors: string[] = [];
|
||||||
allWires.forEach(w => {
|
allWires.forEach(w => {
|
||||||
@ -350,7 +341,7 @@ namespace pxsim.instructions {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
boardDef: allocOpts.boardDef,
|
boardDef: allocOpts.boardDef,
|
||||||
cmpDefs: allocOpts.cmpDefs,
|
cmpDefs: allocOpts.partDefs,
|
||||||
fnArgs: allocOpts.fnArgs,
|
fnArgs: allocOpts.fnArgs,
|
||||||
allAlloc: allocRes,
|
allAlloc: allocRes,
|
||||||
stepToWires: stepToWires,
|
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 {
|
function mkBlankBoardAndBreadboard(boardDef: BoardDefinition, cmpDefs: Map<PartDefinition>, fnArgs: any, width: number, buildMode: boolean = false): visuals.BoardHost {
|
||||||
let state = runtime.board as pxsim.DalBoard;
|
const state = runtime.board as pxsim.CoreBoard;
|
||||||
let boardHost = new visuals.BoardHost({
|
const opts : visuals.BoardHostOpts = {
|
||||||
state: state,
|
state: state,
|
||||||
boardDef: boardDef,
|
boardDef: boardDef,
|
||||||
forceBreadboard: true,
|
forceBreadboard: true,
|
||||||
cmpDefs: cmpDefs,
|
partDefs: cmpDefs,
|
||||||
maxWidth: `${width}px`,
|
maxWidth: `${width}px`,
|
||||||
fnArgs: fnArgs,
|
fnArgs: fnArgs,
|
||||||
wireframe: buildMode,
|
wireframe: buildMode,
|
||||||
});
|
};
|
||||||
|
let boardHost = new visuals.BoardHost(pxsim.visuals.mkBoardView(opts), opts);
|
||||||
let view = boardHost.getView();
|
let view = boardHost.getView();
|
||||||
svg.addClass(view, "board-svg");
|
svg.addClass(view, "board-svg");
|
||||||
|
|
||||||
@ -397,6 +389,19 @@ namespace pxsim.instructions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i <= step; i++) {
|
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];
|
let wires = props.stepToWires[i];
|
||||||
if (wires) {
|
if (wires) {
|
||||||
wires.forEach(w => {
|
wires.forEach(w => {
|
||||||
@ -405,13 +410,12 @@ namespace pxsim.instructions {
|
|||||||
if (i === step) {
|
if (i === step) {
|
||||||
//location highlights
|
//location highlights
|
||||||
if (w.start.type == "breadboard") {
|
if (w.start.type == "breadboard") {
|
||||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.start).rowCol);
|
let lbls = board.highlightBreadboardPin((<BBLoc>w.start));
|
||||||
} else {
|
} else {
|
||||||
board.highlightBoardPin((<BoardLoc>w.start).pin);
|
board.highlightBoardPin((<BoardLoc>w.start).pin);
|
||||||
}
|
}
|
||||||
if (w.end.type == "breadboard") {
|
if (w.end.type == "breadboard") {
|
||||||
let [row, col] = (<BBLoc>w.end).rowCol;
|
let lbls = board.highlightBreadboardPin((<BBLoc>w.end));
|
||||||
let lbls = board.highlightBreadboardPin((<BBLoc>w.end).rowCol);
|
|
||||||
} else {
|
} else {
|
||||||
board.highlightBoardPin((<BoardLoc>w.end).pin);
|
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() {
|
function mkPanel() {
|
||||||
@ -463,7 +449,7 @@ namespace pxsim.instructions {
|
|||||||
cmps.forEach(c => {
|
cmps.forEach(c => {
|
||||||
let quant = 1;
|
let quant = 1;
|
||||||
// TODO: don't special case this
|
// TODO: don't special case this
|
||||||
if (c.visual === "buttonpair") {
|
if (c.visual.builtIn === "buttonpair") {
|
||||||
quant = 2;
|
quant = 2;
|
||||||
}
|
}
|
||||||
let cmp = mkCmpDiv(c.visual, {
|
let cmp = mkCmpDiv(c.visual, {
|
||||||
@ -516,7 +502,7 @@ namespace pxsim.instructions {
|
|||||||
let wires = (props.stepToWires[step] || []);
|
let wires = (props.stepToWires[step] || []);
|
||||||
let mkLabel = (loc: Loc) => {
|
let mkLabel = (loc: Loc) => {
|
||||||
if (loc.type === "breadboard") {
|
if (loc.type === "breadboard") {
|
||||||
let [row, col] = (<BBLoc>loc).rowCol;
|
let {row, col} = (<BBLoc>loc);
|
||||||
return `(${row},${col})`
|
return `(${row},${col})`
|
||||||
} else
|
} else
|
||||||
return (<BoardLoc>loc).pin;
|
return (<BoardLoc>loc).pin;
|
||||||
@ -536,17 +522,23 @@ namespace pxsim.instructions {
|
|||||||
});
|
});
|
||||||
let cmps = (props.stepToCmps[step] || []);
|
let cmps = (props.stepToCmps[step] || []);
|
||||||
cmps.forEach(c => {
|
cmps.forEach(c => {
|
||||||
let l: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn}`];
|
let locs: BBLoc[];
|
||||||
let locs = [l];
|
if (c.visual.builtIn === "buttonpair") {
|
||||||
if (c.visual === "buttonpair") {
|
|
||||||
//TODO: don't special case this
|
//TODO: don't special case this
|
||||||
let l2: BBRowCol = [`${c.breadboardStartRow}`, `${c.breadboardStartColumn + 3}`];
|
locs = [c.breadboardConnections[0], c.breadboardConnections[2]]
|
||||||
locs.push(l2);
|
} else {
|
||||||
|
locs = [c.breadboardConnections[0]];
|
||||||
}
|
}
|
||||||
locs.forEach((l, i) => {
|
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, {
|
let cmp = mkCmpDiv(c.visual, {
|
||||||
top: `(${row},${col})`,
|
top: topLbl,
|
||||||
topSize: LOC_LBL_SIZE,
|
topSize: LOC_LBL_SIZE,
|
||||||
cmpHeight: REQ_CMP_HEIGHT,
|
cmpHeight: REQ_CMP_HEIGHT,
|
||||||
cmpScale: REQ_CMP_SCALE
|
cmpScale: REQ_CMP_SCALE
|
||||||
@ -623,6 +615,9 @@ ${tsPackage}
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// board def
|
||||||
|
const boardDef = JSON.parse(getQsVal("board")) as pxsim.BoardDefinition;
|
||||||
|
|
||||||
//parts list
|
//parts list
|
||||||
let parts = (getQsVal("parts") || "").split(" ");
|
let parts = (getQsVal("parts") || "").split(" ");
|
||||||
parts.sort();
|
parts.sort();
|
||||||
@ -646,7 +641,6 @@ ${tsPackage}
|
|||||||
|
|
||||||
style.textContent += STYLE;
|
style.textContent += STYLE;
|
||||||
|
|
||||||
const boardDef = CURRENT_BOARD;
|
|
||||||
const cmpDefs = partDefinitions;
|
const cmpDefs = partDefinitions;
|
||||||
|
|
||||||
//props
|
//props
|
||||||
@ -656,8 +650,8 @@ ${tsPackage}
|
|||||||
activeComponents.sort();
|
activeComponents.sort();
|
||||||
let props = mkBoardProps({
|
let props = mkBoardProps({
|
||||||
boardDef: boardDef,
|
boardDef: boardDef,
|
||||||
cmpDefs: cmpDefs,
|
partDefs: cmpDefs,
|
||||||
cmpList: activeComponents,
|
partsList: activeComponents,
|
||||||
fnArgs: fnArgs,
|
fnArgs: fnArgs,
|
||||||
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
|
getBBCoord: dummyBreadboard.getCoord.bind(dummyBreadboard)
|
||||||
});
|
});
|
261
sim/simlib.ts
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
namespace pxsim.input {
|
namespace pxsim.input {
|
||||||
export function onButtonPressed(button: number, handler: RefAction): void {
|
export function onButtonPressed(button: number, handler: RefAction): void {
|
||||||
let b = board().buttonPairState;
|
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;
|
b.usesButtonAB = true;
|
||||||
runtime.queueDisplayUpdate();
|
runtime.queueDisplayUpdate();
|
||||||
}
|
}
|
||||||
@ -10,32 +10,12 @@ namespace pxsim.input {
|
|||||||
|
|
||||||
export function buttonIsPressed(button: number): boolean {
|
export function buttonIsPressed(button: number): boolean {
|
||||||
let b = board().buttonPairState;
|
let b = board().buttonPairState;
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_AB && !b.usesButtonAB) {
|
if (button == b.abBtn.id && !b.usesButtonAB) {
|
||||||
b.usesButtonAB = true;
|
b.usesButtonAB = true;
|
||||||
runtime.queueDisplayUpdate();
|
runtime.queueDisplayUpdate();
|
||||||
}
|
}
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_A) return b.aBtn.pressed;
|
if (button == b.aBtn.id) return b.aBtn.pressed;
|
||||||
if (button == DAL.MICROBIT_ID_BUTTON_B) return b.bBtn.pressed;
|
if (button == b.bBtn.id) return b.bBtn.pressed;
|
||||||
return b.abBtn.pressed || (b.aBtn.pressed && b.bBtn.pressed);
|
return b.abBtn.pressed || (b.aBtn.pressed && b.bBtn.pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace pxsim {
|
|
||||||
export class Button {
|
|
||||||
constructor(public id: number) { }
|
|
||||||
pressed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ButtonPairState {
|
|
||||||
usesButtonAB: boolean = false;
|
|
||||||
aBtn: Button;
|
|
||||||
bBtn: Button;
|
|
||||||
abBtn: Button;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.aBtn = new Button(DAL.MICROBIT_ID_BUTTON_A);
|
|
||||||
this.bBtn = new Button(DAL.MICROBIT_ID_BUTTON_B);
|
|
||||||
this.abBtn = new Button(DAL.MICROBIT_ID_BUTTON_AB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,11 +12,4 @@ namespace pxsim.input {
|
|||||||
// TODO
|
// TODO
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
namespace pxsim {
|
|
||||||
export class CompassState {
|
|
||||||
usesHeading = false;
|
|
||||||
heading = 90;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -24,64 +24,6 @@ namespace pxsim {
|
|||||||
export function getPin(id: number) {
|
export function getPin(id: number) {
|
||||||
return board().edgeConnectorState.getPin(id);
|
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 {
|
namespace pxsim.pins {
|
||||||
|
@ -116,72 +116,6 @@ namespace pxsim {
|
|||||||
}
|
}
|
||||||
return font;
|
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 {
|
namespace pxsim.images {
|
||||||
@ -337,6 +271,7 @@ namespace pxsim.led {
|
|||||||
|
|
||||||
export function stopAnimation(): void {
|
export function stopAnimation(): void {
|
||||||
board().ledMatrixState.animationQ.cancelAll();
|
board().ledMatrixState.animationQ.cancelAll();
|
||||||
|
board().ledMatrixState.image.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDisplayMode(mode: DisplayMode): void {
|
export function setDisplayMode(mode: DisplayMode): void {
|
||||||
|
@ -1,10 +1,3 @@
|
|||||||
namespace pxsim {
|
|
||||||
export class LightSensorState {
|
|
||||||
usesLightLevel = false;
|
|
||||||
lightLevel = 128;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace pxsim.input {
|
namespace pxsim.input {
|
||||||
export function lightLevel(): number {
|
export function lightLevel(): number {
|
||||||
let b = board().lightSensorState;
|
let b = board().lightSensorState;
|
||||||
|
@ -22,80 +22,9 @@ namespace pxsim {
|
|||||||
throw new Error("PANIC " + code)
|
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 {
|
export interface RuntimeOptions {
|
||||||
theme: string;
|
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 {
|
namespace pxsim.basic {
|
||||||
|
@ -4,54 +4,10 @@ namespace pxsim {
|
|||||||
if (b) {
|
if (b) {
|
||||||
let np = b.neopixelState;
|
let np = b.neopixelState;
|
||||||
if (np) {
|
if (np) {
|
||||||
np.updateBuffer(buffer, pin);
|
let buf = <Uint8Array[]>(<any>buffer).data;
|
||||||
|
np.updateBuffer(buf, pin);
|
||||||
runtime.queueDisplayUpdate();
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,649 +0,0 @@
|
|||||||
namespace pxsim.visuals {
|
|
||||||
// The distance between the center of two pins. This is the constant on which everything else is based.
|
|
||||||
const PIN_DIST = 15;
|
|
||||||
// CSS styling for the breadboard
|
|
||||||
const BLUE = "#1AA5D7";
|
|
||||||
const RED = "#DD4BA0";
|
|
||||||
const BREADBOARD_CSS = `
|
|
||||||
/* bread board */
|
|
||||||
.sim-bb-background {
|
|
||||||
fill:#E0E0E0;
|
|
||||||
}
|
|
||||||
.sim-bb-pin {
|
|
||||||
fill:#999;
|
|
||||||
}
|
|
||||||
.sim-bb-pin-hover {
|
|
||||||
visibility: hidden;
|
|
||||||
pointer-events: all;
|
|
||||||
stroke-width: ${PIN_DIST / 2}px;
|
|
||||||
stroke: transparent;
|
|
||||||
fill: #777;
|
|
||||||
}
|
|
||||||
.sim-bb-pin-hover:hover {
|
|
||||||
visibility: visible;
|
|
||||||
fill:#444;
|
|
||||||
}
|
|
||||||
.sim-bb-group-wire {
|
|
||||||
stroke: #999;
|
|
||||||
stroke-width: ${PIN_DIST / 4}px;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.sim-bb-pin-group {
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
.sim-bb-label,
|
|
||||||
.sim-bb-label-hover {
|
|
||||||
font-family:"Lucida Console", Monaco, monospace;
|
|
||||||
fill:#555;
|
|
||||||
pointer-events: all;
|
|
||||||
stroke-width: 0;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.sim-bb-label-hover {
|
|
||||||
visibility: hidden;
|
|
||||||
fill:#000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.sim-bb-bar {
|
|
||||||
stroke-width: 0;
|
|
||||||
}
|
|
||||||
.sim-bb-blue {
|
|
||||||
fill:${BLUE};
|
|
||||||
stroke:${BLUE}
|
|
||||||
}
|
|
||||||
.sim-bb-red {
|
|
||||||
fill:${RED};
|
|
||||||
stroke:${RED};
|
|
||||||
}
|
|
||||||
.sim-bb-pin-group:hover .sim-bb-pin-hover,
|
|
||||||
.sim-bb-pin-group:hover .sim-bb-group-wire,
|
|
||||||
.sim-bb-pin-group:hover .sim-bb-label-hover {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.sim-bb-pin-group:hover .sim-bb-label {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
/* outline mode */
|
|
||||||
.sim-bb-outline .sim-bb-background {
|
|
||||||
stroke-width: ${PIN_DIST / 7}px;
|
|
||||||
fill: #FFF;
|
|
||||||
stroke: #000;
|
|
||||||
}
|
|
||||||
.sim-bb-outline .sim-bb-mid-channel {
|
|
||||||
fill: #FFF;
|
|
||||||
stroke: #888;
|
|
||||||
stroke-width: 1px;
|
|
||||||
}
|
|
||||||
/* grayed out */
|
|
||||||
.grayed .sim-bb-red,
|
|
||||||
.grayed .sim-bb-blue {
|
|
||||||
fill: #BBB;
|
|
||||||
}
|
|
||||||
.grayed .sim-bb-pin {
|
|
||||||
fill: #BBB;
|
|
||||||
}
|
|
||||||
.grayed .sim-bb-label {
|
|
||||||
fill: #BBB;
|
|
||||||
}
|
|
||||||
.grayed .sim-bb-background {
|
|
||||||
stroke: #BBB;
|
|
||||||
}
|
|
||||||
.grayed .sim-bb-group-wire {
|
|
||||||
stroke: #DDD;
|
|
||||||
}
|
|
||||||
/* highlighted */
|
|
||||||
.sim-bb-label.highlight {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.sim-bb-label-hover.highlight {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.sim-bb-blue.highlight {
|
|
||||||
fill:${BLUE};
|
|
||||||
}
|
|
||||||
.sim-bb-red.highlight {
|
|
||||||
fill:${RED};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
// Pin rows and coluns
|
|
||||||
const MID_ROWS = 10;
|
|
||||||
const MID_ROW_GAPS = [4, 4];
|
|
||||||
const MID_ROW_AND_GAPS = MID_ROWS + MID_ROW_GAPS.length;
|
|
||||||
const MID_COLS = 30;
|
|
||||||
const BAR_ROWS = 2;
|
|
||||||
const BAR_COLS = 25;
|
|
||||||
const POWER_ROWS = BAR_ROWS * 2;
|
|
||||||
const POWER_COLS = BAR_COLS * 2;
|
|
||||||
const BAR_COL_GAPS = [4, 9, 14, 19];
|
|
||||||
const BAR_COL_AND_GAPS = BAR_COLS + BAR_COL_GAPS.length;
|
|
||||||
// Essential dimensions
|
|
||||||
const WIDTH = PIN_DIST * (MID_COLS + 3);
|
|
||||||
const HEIGHT = PIN_DIST * (MID_ROW_AND_GAPS + POWER_ROWS + 5.5);
|
|
||||||
const MID_RATIO = 2.0 / 3.0;
|
|
||||||
const BAR_RATIO = (1.0 - MID_RATIO) * 0.5;
|
|
||||||
const MID_HEIGHT = HEIGHT * MID_RATIO;
|
|
||||||
const BAR_HEIGHT = HEIGHT * BAR_RATIO;
|
|
||||||
// Pin grids
|
|
||||||
const MID_GRID_WIDTH = (MID_COLS - 1) * PIN_DIST;
|
|
||||||
const MID_GRID_HEIGHT = (MID_ROW_AND_GAPS - 1) * PIN_DIST;
|
|
||||||
const MID_GRID_X = (WIDTH - MID_GRID_WIDTH) / 2.0;
|
|
||||||
const MID_GRID_Y = BAR_HEIGHT + (MID_HEIGHT - MID_GRID_HEIGHT) / 2.0;
|
|
||||||
const BAR_GRID_HEIGHT = (BAR_ROWS - 1) * PIN_DIST;
|
|
||||||
const BAR_GRID_WIDTH = (BAR_COL_AND_GAPS - 1) * PIN_DIST;
|
|
||||||
const BAR_TOP_GRID_X = (WIDTH - BAR_GRID_WIDTH) / 2.0;
|
|
||||||
const BAR_TOP_GRID_Y = (BAR_HEIGHT - BAR_GRID_HEIGHT) / 2.0;
|
|
||||||
const BAR_BOT_GRID_X = BAR_TOP_GRID_X;
|
|
||||||
const BAR_BOT_GRID_Y = BAR_TOP_GRID_Y + BAR_HEIGHT + MID_HEIGHT;
|
|
||||||
// Individual pins
|
|
||||||
const PIN_HOVER_SCALAR = 1.3;
|
|
||||||
const PIN_WIDTH = PIN_DIST / 2.5;
|
|
||||||
const PIN_ROUNDING = PIN_DIST / 7.5;
|
|
||||||
// Labels
|
|
||||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
|
||||||
const PIN_LBL_HOVER_SCALAR = 1.3;
|
|
||||||
const PLUS_LBL_SIZE = PIN_DIST * 1.7;
|
|
||||||
const MINUS_LBL_SIZE = PIN_DIST * 2;
|
|
||||||
const POWER_LBL_OFFSET = PIN_DIST * 0.8;
|
|
||||||
const MINUS_LBL_EXTRA_OFFSET = PIN_DIST * 0.07;
|
|
||||||
const LBL_ROTATION = -90;
|
|
||||||
// Channels
|
|
||||||
const CHANNEL_HEIGHT = PIN_DIST * 1.0;
|
|
||||||
const SMALL_CHANNEL_HEIGHT = PIN_DIST * 0.05;
|
|
||||||
// Background
|
|
||||||
const BACKGROUND_ROUNDING = PIN_DIST * 0.3;
|
|
||||||
|
|
||||||
export interface GridPin {
|
|
||||||
el: SVGElement,
|
|
||||||
hoverEl: SVGElement,
|
|
||||||
cx: number,
|
|
||||||
cy: number,
|
|
||||||
row: string,
|
|
||||||
col: string,
|
|
||||||
group?: string
|
|
||||||
};
|
|
||||||
export interface GridOptions {
|
|
||||||
xOffset?: number,
|
|
||||||
yOffset?: number,
|
|
||||||
rowCount: number,
|
|
||||||
colCount: number,
|
|
||||||
rowStartIdx?: number,
|
|
||||||
colStartIdx?: number,
|
|
||||||
pinDist: number,
|
|
||||||
mkPin: () => SVGElAndSize,
|
|
||||||
mkHoverPin: () => SVGElAndSize,
|
|
||||||
getRowName: (rowIdx: number) => string,
|
|
||||||
getColName: (colIdx: number) => string,
|
|
||||||
getGroupName?: (rowIdx: number, colIdx: number) => string,
|
|
||||||
rowIdxsWithGap?: number[],
|
|
||||||
colIdxsWithGap?: number[],
|
|
||||||
};
|
|
||||||
export interface GridResult {
|
|
||||||
g: SVGGElement,
|
|
||||||
allPins: GridPin[],
|
|
||||||
}
|
|
||||||
export function mkGrid(opts: GridOptions): GridResult {
|
|
||||||
let xOff = opts.xOffset || 0;
|
|
||||||
let yOff = opts.yOffset || 0;
|
|
||||||
let allPins: GridPin[] = [];
|
|
||||||
let grid = <SVGGElement>svg.elt("g");
|
|
||||||
let colIdxOffset = opts.colStartIdx || 0;
|
|
||||||
let rowIdxOffset = opts.rowStartIdx || 0;
|
|
||||||
let copyArr = <T>(arr: T[]): T[] => arr ? arr.slice(0, arr.length) : [];
|
|
||||||
let removeAll = <T>(arr: T[], e: T): number => {
|
|
||||||
let res = 0;
|
|
||||||
let idx: number;
|
|
||||||
while (0 <= (idx = arr.indexOf(e))) {
|
|
||||||
arr.splice(idx, 1);
|
|
||||||
res += 1;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
let rowGaps = 0;
|
|
||||||
let rowIdxsWithGap = copyArr(opts.rowIdxsWithGap)
|
|
||||||
for (let i = 0; i < opts.rowCount; i++) {
|
|
||||||
let colGaps = 0;
|
|
||||||
let colIdxsWithGap = copyArr(opts.colIdxsWithGap)
|
|
||||||
let cy = yOff + i * opts.pinDist + rowGaps * opts.pinDist;
|
|
||||||
let rowIdx = i + rowIdxOffset;
|
|
||||||
for (let j = 0; j < opts.colCount; j++) {
|
|
||||||
let cx = xOff + j * opts.pinDist + colGaps * opts.pinDist;
|
|
||||||
let colIdx = j + colIdxOffset;
|
|
||||||
const addEl = (pin: SVGElAndSize) => {
|
|
||||||
let pinX = cx - pin.w * 0.5;
|
|
||||||
let pinY = cy - pin.h * 0.5;
|
|
||||||
svg.hydrate(pin.el, {x: pinX, y: pinY});
|
|
||||||
grid.appendChild(pin.el);
|
|
||||||
return pin.el;
|
|
||||||
}
|
|
||||||
let el = addEl(opts.mkPin());
|
|
||||||
let hoverEl = addEl(opts.mkHoverPin());
|
|
||||||
let row = opts.getRowName(rowIdx);
|
|
||||||
let col = opts.getColName(colIdx);
|
|
||||||
let group = opts.getGroupName ? opts.getGroupName(rowIdx, colIdx) : null;
|
|
||||||
let gridPin: GridPin = {el: el, hoverEl: hoverEl, cx: cx, cy: cy, row: row, col: col, group: group};
|
|
||||||
allPins.push(gridPin);
|
|
||||||
//column gaps
|
|
||||||
colGaps += removeAll(colIdxsWithGap, colIdx);
|
|
||||||
}
|
|
||||||
//row gaps
|
|
||||||
rowGaps += removeAll(rowIdxsWithGap, rowIdx);
|
|
||||||
}
|
|
||||||
return {g: grid, allPins: allPins};
|
|
||||||
}
|
|
||||||
function mkBBPin(): SVGElAndSize {
|
|
||||||
let el = svg.elt("rect");
|
|
||||||
let width = PIN_WIDTH;
|
|
||||||
svg.hydrate(el, {
|
|
||||||
class: "sim-bb-pin",
|
|
||||||
rx: PIN_ROUNDING,
|
|
||||||
ry: PIN_ROUNDING,
|
|
||||||
width: width,
|
|
||||||
height: width
|
|
||||||
});
|
|
||||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
|
||||||
}
|
|
||||||
function mkBBHoverPin(): SVGElAndSize {
|
|
||||||
let el = svg.elt("rect");
|
|
||||||
let width = PIN_WIDTH * PIN_HOVER_SCALAR;
|
|
||||||
svg.hydrate(el, {
|
|
||||||
class: "sim-bb-pin-hover",
|
|
||||||
rx: PIN_ROUNDING,
|
|
||||||
ry: PIN_ROUNDING,
|
|
||||||
width: width,
|
|
||||||
height: width,
|
|
||||||
});
|
|
||||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
|
||||||
}
|
|
||||||
export interface GridLabel {
|
|
||||||
el: SVGTextElement,
|
|
||||||
hoverEl: SVGTextElement,
|
|
||||||
txt: string,
|
|
||||||
group?: string,
|
|
||||||
};
|
|
||||||
function mkBBLabel(cx: number, cy: number, size: number, rotation: number, txt: string, group: string, extraClasses?: string[]): GridLabel {
|
|
||||||
//lbl
|
|
||||||
let el = mkTxt(cx, cy, size, rotation, txt);
|
|
||||||
svg.addClass(el, "sim-bb-label");
|
|
||||||
if (extraClasses)
|
|
||||||
extraClasses.forEach(c => svg.addClass(el, c));
|
|
||||||
|
|
||||||
//hover lbl
|
|
||||||
let hoverEl = mkTxt(cx, cy, size * PIN_LBL_HOVER_SCALAR, rotation, txt);
|
|
||||||
svg.addClass(hoverEl, "sim-bb-label-hover");
|
|
||||||
if (extraClasses)
|
|
||||||
extraClasses.forEach(c => svg.addClass(hoverEl, c));
|
|
||||||
|
|
||||||
let lbl = {el: el, hoverEl: hoverEl, txt: txt, group: group};
|
|
||||||
return lbl;
|
|
||||||
}
|
|
||||||
interface BBBar {
|
|
||||||
el: SVGRectElement,
|
|
||||||
group?: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface BreadboardOpts {
|
|
||||||
wireframe?: boolean,
|
|
||||||
}
|
|
||||||
export class Breadboard {
|
|
||||||
public bb: SVGSVGElement;
|
|
||||||
private styleEl: SVGStyleElement;
|
|
||||||
private defs: SVGDefsElement;
|
|
||||||
|
|
||||||
//truth
|
|
||||||
private allPins: GridPin[] = [];
|
|
||||||
private allLabels: GridLabel[] = [];
|
|
||||||
private allPowerBars: BBBar[] = [];
|
|
||||||
//quick lookup caches
|
|
||||||
private rowColToPin: Map<Map<GridPin>> = {};
|
|
||||||
private rowColToLbls: Map<Map<GridLabel[]>> = {};
|
|
||||||
|
|
||||||
constructor(opts: BreadboardOpts) {
|
|
||||||
this.buildDom();
|
|
||||||
|
|
||||||
if (opts.wireframe)
|
|
||||||
svg.addClass(this.bb, "sim-bb-outline");
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateLocation(x: number, y: number) {
|
|
||||||
svg.hydrate(this.bb, {
|
|
||||||
x: `${x}px`,
|
|
||||||
y: `${y}px`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPin(row: string, col: string): GridPin {
|
|
||||||
let colToPin = this.rowColToPin[row];
|
|
||||||
if (!colToPin)
|
|
||||||
return null;
|
|
||||||
let pin = colToPin[col];
|
|
||||||
if (!pin)
|
|
||||||
return null;
|
|
||||||
return pin;
|
|
||||||
}
|
|
||||||
public getCoord(rowCol: BBRowCol): Coord {
|
|
||||||
let [row, col] = rowCol;
|
|
||||||
let pin = this.getPin(row, col);
|
|
||||||
if (!pin)
|
|
||||||
return null;
|
|
||||||
return [pin.cx, pin.cy];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPinDist() {
|
|
||||||
return PIN_DIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildDom() {
|
|
||||||
this.bb = <SVGSVGElement>svg.elt("svg", {
|
|
||||||
"version": "1.0",
|
|
||||||
"viewBox": `0 0 ${WIDTH} ${HEIGHT}`,
|
|
||||||
"class": `sim-bb`,
|
|
||||||
"width": WIDTH + "px",
|
|
||||||
"height": HEIGHT + "px",
|
|
||||||
});
|
|
||||||
this.styleEl = <SVGStyleElement>svg.child(this.bb, "style", {});
|
|
||||||
this.styleEl.textContent += BREADBOARD_CSS;
|
|
||||||
this.defs = <SVGDefsElement>svg.child(this.bb, "defs", {});
|
|
||||||
|
|
||||||
//background
|
|
||||||
svg.child(this.bb, "rect", { class: "sim-bb-background", width: WIDTH, height: HEIGHT, rx: BACKGROUND_ROUNDING, ry: BACKGROUND_ROUNDING});
|
|
||||||
|
|
||||||
//mid channel
|
|
||||||
let channelGid = "sim-bb-channel-grad";
|
|
||||||
let channelGrad = <SVGLinearGradientElement>svg.elt("linearGradient")
|
|
||||||
svg.hydrate(channelGrad, { id: channelGid, x1: "0%", y1: "0%", x2: "0%", y2: "100%" });
|
|
||||||
this.defs.appendChild(channelGrad);
|
|
||||||
let channelDark = "#AAA";
|
|
||||||
let channelLight = "#CCC";
|
|
||||||
let stop1 = svg.child(channelGrad, "stop", { offset: "0%", style: `stop-color: ${channelDark};` })
|
|
||||||
let stop2 = svg.child(channelGrad, "stop", { offset: "20%", style: `stop-color: ${channelLight};` })
|
|
||||||
let stop3 = svg.child(channelGrad, "stop", { offset: "80%", style: `stop-color: ${channelLight};` })
|
|
||||||
let stop4 = svg.child(channelGrad, "stop", { offset: "100%", style: `stop-color: ${channelDark};` })
|
|
||||||
|
|
||||||
const mkChannel = (cy: number, h: number, cls?: string) => {
|
|
||||||
let channel = svg.child(this.bb, "rect", { class: `sim-bb-channel ${cls || ""}`, y: cy - h / 2, width: WIDTH, height: h});
|
|
||||||
channel.setAttribute("fill", `url(#${channelGid})`);
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
mkChannel(BAR_HEIGHT + MID_HEIGHT / 2, CHANNEL_HEIGHT, "sim-bb-mid-channel");
|
|
||||||
mkChannel(BAR_HEIGHT, SMALL_CHANNEL_HEIGHT);
|
|
||||||
mkChannel(BAR_HEIGHT + MID_HEIGHT, SMALL_CHANNEL_HEIGHT);
|
|
||||||
|
|
||||||
//-----pins
|
|
||||||
const getMidTopOrBot = (rowIdx: number) => rowIdx < MID_ROWS / 2.0 ? "b" : "t";
|
|
||||||
const getBarTopOrBot = (colIdx: number) => colIdx < POWER_COLS / 2.0 ? "b" : "t";
|
|
||||||
const alphabet = "abcdefghij".split("").reverse();
|
|
||||||
const getColName = (colIdx: number) => `${colIdx + 1}`;
|
|
||||||
const getMidRowName = (rowIdx: number) => alphabet[rowIdx];
|
|
||||||
const getMidGroupName = (rowIdx: number, colIdx: number) => {
|
|
||||||
let botOrTop = getMidTopOrBot(rowIdx);
|
|
||||||
let colNm = getColName(colIdx);
|
|
||||||
return `${botOrTop}${colNm}`;
|
|
||||||
};
|
|
||||||
const getBarRowName = (rowIdx: number) => rowIdx === 0 ? "-" : "+";
|
|
||||||
const getBarGroupName = (rowIdx: number, colIdx: number) => {
|
|
||||||
let botOrTop = getBarTopOrBot(colIdx);
|
|
||||||
let rowName = getBarRowName(rowIdx);
|
|
||||||
return `${rowName}${botOrTop}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
//mid grid
|
|
||||||
let midGridRes = mkGrid({
|
|
||||||
xOffset: MID_GRID_X,
|
|
||||||
yOffset: MID_GRID_Y,
|
|
||||||
rowCount: MID_ROWS,
|
|
||||||
colCount: MID_COLS,
|
|
||||||
pinDist: PIN_DIST,
|
|
||||||
mkPin: mkBBPin,
|
|
||||||
mkHoverPin: mkBBHoverPin,
|
|
||||||
getRowName: getMidRowName,
|
|
||||||
getColName: getColName,
|
|
||||||
getGroupName: getMidGroupName,
|
|
||||||
rowIdxsWithGap: MID_ROW_GAPS,
|
|
||||||
});
|
|
||||||
let midGridG = midGridRes.g;
|
|
||||||
this.allPins = this.allPins.concat(midGridRes.allPins);
|
|
||||||
|
|
||||||
//bot bar
|
|
||||||
let botBarGridRes = mkGrid({
|
|
||||||
xOffset: BAR_BOT_GRID_X,
|
|
||||||
yOffset: BAR_BOT_GRID_Y,
|
|
||||||
rowCount: BAR_ROWS,
|
|
||||||
colCount: BAR_COLS,
|
|
||||||
pinDist: PIN_DIST,
|
|
||||||
mkPin: mkBBPin,
|
|
||||||
mkHoverPin: mkBBHoverPin,
|
|
||||||
getRowName: getBarRowName,
|
|
||||||
getColName: getColName,
|
|
||||||
getGroupName: getBarGroupName,
|
|
||||||
colIdxsWithGap: BAR_COL_GAPS,
|
|
||||||
});
|
|
||||||
let botBarGridG = botBarGridRes.g;
|
|
||||||
this.allPins = this.allPins.concat(botBarGridRes.allPins);
|
|
||||||
|
|
||||||
//top bar
|
|
||||||
let topBarGridRes = mkGrid({
|
|
||||||
xOffset: BAR_TOP_GRID_X,
|
|
||||||
yOffset: BAR_TOP_GRID_Y,
|
|
||||||
rowCount: BAR_ROWS,
|
|
||||||
colCount: BAR_COLS,
|
|
||||||
colStartIdx: BAR_COLS,
|
|
||||||
pinDist: PIN_DIST,
|
|
||||||
mkPin: mkBBPin,
|
|
||||||
mkHoverPin: mkBBHoverPin,
|
|
||||||
getRowName: getBarRowName,
|
|
||||||
getColName: getColName,
|
|
||||||
getGroupName: getBarGroupName,
|
|
||||||
colIdxsWithGap: BAR_COL_GAPS.map(g => g + BAR_COLS),
|
|
||||||
});
|
|
||||||
let topBarGridG = topBarGridRes.g;
|
|
||||||
this.allPins = this.allPins.concat(topBarGridRes.allPins);
|
|
||||||
|
|
||||||
//tooltip
|
|
||||||
this.allPins.forEach(pin => {
|
|
||||||
let {el, row, col, hoverEl} = pin
|
|
||||||
let title = `(${row},${col})`;
|
|
||||||
svg.hydrate(el, {title: title});
|
|
||||||
svg.hydrate(hoverEl, {title: title});
|
|
||||||
})
|
|
||||||
|
|
||||||
//catalog pins
|
|
||||||
this.allPins.forEach(pin => {
|
|
||||||
let colToPin = this.rowColToPin[pin.row];
|
|
||||||
if (!colToPin)
|
|
||||||
colToPin = this.rowColToPin[pin.row] = {};
|
|
||||||
colToPin[pin.col] = pin;
|
|
||||||
})
|
|
||||||
|
|
||||||
//-----labels
|
|
||||||
const mkBBLabelAtPin = (row: string, col: string, xOffset: number, yOffset: number, txt: string, group?: string): GridLabel => {
|
|
||||||
let size = PIN_LBL_SIZE;
|
|
||||||
let rotation = LBL_ROTATION;
|
|
||||||
let loc = this.getCoord([row, col]);
|
|
||||||
let [cx, cy] = loc;
|
|
||||||
let t = mkBBLabel(cx + xOffset, cy + yOffset, size, rotation, txt, group);
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
//columns
|
|
||||||
for (let colIdx = 0; colIdx < MID_COLS; colIdx++) {
|
|
||||||
let colNm = getColName(colIdx);
|
|
||||||
//top
|
|
||||||
let rowTIdx = 0;
|
|
||||||
let rowTNm = getMidRowName(rowTIdx);
|
|
||||||
let groupT = getMidGroupName(rowTIdx, colIdx);
|
|
||||||
let lblT = mkBBLabelAtPin(rowTNm, colNm, 0, -PIN_DIST, colNm, groupT);
|
|
||||||
this.allLabels.push(lblT);
|
|
||||||
//bottom
|
|
||||||
let rowBIdx = MID_ROWS - 1;
|
|
||||||
let rowBNm = getMidRowName(rowBIdx);
|
|
||||||
let groupB = getMidGroupName(rowBIdx, colIdx);
|
|
||||||
let lblB = mkBBLabelAtPin(rowBNm, colNm, 0, +PIN_DIST, colNm, groupB);
|
|
||||||
this.allLabels.push(lblB);
|
|
||||||
}
|
|
||||||
//rows
|
|
||||||
for (let rowIdx = 0; rowIdx < MID_ROWS; rowIdx++) {
|
|
||||||
let rowNm = getMidRowName(rowIdx);
|
|
||||||
//top
|
|
||||||
let colTIdx = 0;
|
|
||||||
let colTNm = getColName(colTIdx);
|
|
||||||
let lblT = mkBBLabelAtPin(rowNm, colTNm, -PIN_DIST, 0, rowNm);
|
|
||||||
this.allLabels.push(lblT);
|
|
||||||
//top
|
|
||||||
let colBIdx = MID_COLS - 1;
|
|
||||||
let colBNm = getColName(colBIdx);
|
|
||||||
let lblB = mkBBLabelAtPin(rowNm, colBNm, +PIN_DIST, 0, rowNm);
|
|
||||||
this.allLabels.push(lblB);
|
|
||||||
}
|
|
||||||
|
|
||||||
//+- labels
|
|
||||||
let botPowerLabels = [
|
|
||||||
//BL
|
|
||||||
mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, 0), [`sim-bb-blue`]),
|
|
||||||
mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, 0), [`sim-bb-red`]),
|
|
||||||
//BR
|
|
||||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS - 1), [`sim-bb-blue`]),
|
|
||||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS - 1), [`sim-bb-red`]),
|
|
||||||
];
|
|
||||||
this.allLabels = this.allLabels.concat(botPowerLabels);
|
|
||||||
let topPowerLabels = [
|
|
||||||
//TL
|
|
||||||
mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS), [`sim-bb-blue`]),
|
|
||||||
mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS), [`sim-bb-red`]),
|
|
||||||
//TR
|
|
||||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, POWER_COLS - 1), [`sim-bb-blue`]),
|
|
||||||
mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, POWER_COLS - 1), [`sim-bb-red`]),
|
|
||||||
];
|
|
||||||
this.allLabels = this.allLabels.concat(topPowerLabels);
|
|
||||||
|
|
||||||
//catalog lbls
|
|
||||||
let lblNmToLbls: Map<GridLabel[]> = {};
|
|
||||||
this.allLabels.forEach(lbl => {
|
|
||||||
let {el, txt} = lbl;
|
|
||||||
let lbls = lblNmToLbls[txt] = lblNmToLbls[txt] || []
|
|
||||||
lbls.push(lbl);
|
|
||||||
});
|
|
||||||
const isPowerPin = (pin: GridPin) => pin.row === "-" || pin.row === "+";
|
|
||||||
this.allPins.forEach(pin => {
|
|
||||||
let {row, col, group} = pin;
|
|
||||||
let colToLbls = this.rowColToLbls[row] || (this.rowColToLbls[row] = {});
|
|
||||||
let lbls = colToLbls[col] || (colToLbls[col] = []);
|
|
||||||
if (isPowerPin(pin)) {
|
|
||||||
//power pins
|
|
||||||
let isBot = Number(col) <= BAR_COLS;
|
|
||||||
if (isBot)
|
|
||||||
botPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
|
|
||||||
else
|
|
||||||
topPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
|
|
||||||
} else {
|
|
||||||
//mid pins
|
|
||||||
let rowLbls = lblNmToLbls[row];
|
|
||||||
rowLbls.forEach(l => lbls.push(l));
|
|
||||||
let colLbls = lblNmToLbls[col];
|
|
||||||
colLbls.forEach(l => lbls.push(l));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//-----blue & red lines
|
|
||||||
const lnLen = BAR_GRID_WIDTH + PIN_DIST * 1.5;
|
|
||||||
const lnThickness = PIN_DIST / 5.0;
|
|
||||||
const lnYOff = PIN_DIST * 0.6;
|
|
||||||
const lnXOff = (lnLen - BAR_GRID_WIDTH) / 2.0;
|
|
||||||
const mkPowerLine = (x: number, y: number, group: string, cls: string): BBBar => {
|
|
||||||
let ln = <SVGRectElement>svg.elt("rect");
|
|
||||||
svg.hydrate(ln, {
|
|
||||||
class: `sim-bb-bar ${cls}`,
|
|
||||||
x: x,
|
|
||||||
y: y - lnThickness / 2.0,
|
|
||||||
width: lnLen,
|
|
||||||
height: lnThickness});
|
|
||||||
let bar: BBBar = {el: ln, group: group};
|
|
||||||
return bar;
|
|
||||||
}
|
|
||||||
let barLines = [
|
|
||||||
//top
|
|
||||||
mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y - lnYOff, getBarGroupName(0, POWER_COLS - 1), "sim-bb-blue"),
|
|
||||||
mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, POWER_COLS - 1), "sim-bb-red"),
|
|
||||||
//bot
|
|
||||||
mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y - lnYOff, getBarGroupName(0, 0), "sim-bb-blue"),
|
|
||||||
mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, 0), "sim-bb-red"),
|
|
||||||
];
|
|
||||||
this.allPowerBars = this.allPowerBars.concat(barLines);
|
|
||||||
//attach power bars
|
|
||||||
this.allPowerBars.forEach(b => this.bb.appendChild(b.el));
|
|
||||||
|
|
||||||
//-----electrically connected groups
|
|
||||||
//make groups
|
|
||||||
let allGrpNms = this.allPins.map(p => p.group).filter((g, i, a) => a.indexOf(g) == i);
|
|
||||||
let groups: SVGGElement[] = allGrpNms.map(grpNm => {
|
|
||||||
let g = <SVGGElement>svg.elt("g");
|
|
||||||
return g;
|
|
||||||
});
|
|
||||||
groups.forEach(g => svg.addClass(g, "sim-bb-pin-group"));
|
|
||||||
groups.forEach((g, i) => svg.addClass(g, `group-${allGrpNms[i]}`));
|
|
||||||
let grpNmToGroup: Map<SVGGElement> = {};
|
|
||||||
allGrpNms.forEach((g, i) => grpNmToGroup[g] = groups[i]);
|
|
||||||
//group pins and add connecting wire
|
|
||||||
let grpNmToPins: Map<GridPin[]> = {};
|
|
||||||
this.allPins.forEach((p, i) => {
|
|
||||||
let g = p.group;
|
|
||||||
let pins = grpNmToPins[g] || (grpNmToPins[g] = []);
|
|
||||||
pins.push(p);
|
|
||||||
});
|
|
||||||
//connecting wire
|
|
||||||
allGrpNms.forEach(grpNm => {
|
|
||||||
let pins = grpNmToPins[grpNm];
|
|
||||||
let [xs, ys] = [pins.map(p => p.cx), pins.map(p => p.cy)];
|
|
||||||
let minFn = (arr: number[]) => arr.reduce((a, b) => a < b ? a : b);
|
|
||||||
let maxFn = (arr: number[]) => arr.reduce((a, b) => a > b ? a : b);
|
|
||||||
let [minX, maxX, minY, maxY] = [minFn(xs), maxFn(xs), minFn(ys), maxFn(ys)];
|
|
||||||
let wire = svg.elt("rect");
|
|
||||||
let width = Math.max(maxX - minX, 0.0001/*rects with no width aren't displayed*/);
|
|
||||||
let height = Math.max(maxY - minY, 0.0001);
|
|
||||||
svg.hydrate(wire, {x: minX, y: minY, width: width, height: height});
|
|
||||||
svg.addClass(wire, "sim-bb-group-wire")
|
|
||||||
let g = grpNmToGroup[grpNm];
|
|
||||||
g.appendChild(wire);
|
|
||||||
});
|
|
||||||
//group pins
|
|
||||||
this.allPins.forEach(p => {
|
|
||||||
let g = grpNmToGroup[p.group];
|
|
||||||
g.appendChild(p.el);
|
|
||||||
g.appendChild(p.hoverEl);
|
|
||||||
})
|
|
||||||
//group lbls
|
|
||||||
let miscLblGroup = <SVGGElement>svg.elt("g");
|
|
||||||
svg.hydrate(miscLblGroup, {class: "sim-bb-group-misc"});
|
|
||||||
groups.push(miscLblGroup);
|
|
||||||
this.allLabels.forEach(l => {
|
|
||||||
if (l.group) {
|
|
||||||
let g = grpNmToGroup[l.group];
|
|
||||||
g.appendChild(l.el);
|
|
||||||
g.appendChild(l.hoverEl);
|
|
||||||
} else {
|
|
||||||
miscLblGroup.appendChild(l.el);
|
|
||||||
miscLblGroup.appendChild(l.hoverEl);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//attach to bb
|
|
||||||
groups.forEach(g => this.bb.appendChild(g)); //attach to breadboard
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSVGAndSize(): SVGAndSize<SVGSVGElement> {
|
|
||||||
return {el: this.bb, y: 0, x: 0, w: WIDTH, h: HEIGHT};
|
|
||||||
}
|
|
||||||
|
|
||||||
public highlightLoc(rowCol: BBRowCol) {
|
|
||||||
let [row, col] = rowCol;
|
|
||||||
let pin = this.rowColToPin[row][col];
|
|
||||||
let {cx, cy} = pin;
|
|
||||||
let lbls = this.rowColToLbls[row][col];
|
|
||||||
const highlightLbl = (lbl: GridLabel) => {
|
|
||||||
svg.addClass(lbl.el, "highlight");
|
|
||||||
svg.addClass(lbl.hoverEl, "highlight");
|
|
||||||
};
|
|
||||||
lbls.forEach(highlightLbl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,204 +0,0 @@
|
|||||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
|
||||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
|
||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
|
||||||
|
|
||||||
namespace pxsim.visuals {
|
|
||||||
export function mkBtnSvg(xy: Coord): SVGAndSize<SVGGElement> {
|
|
||||||
let [innerCls, outerCls] = ["sim-button", "sim-button-outer"];
|
|
||||||
const tabSize = PIN_DIST / 2.5;
|
|
||||||
const pegR = PIN_DIST / 5;
|
|
||||||
const btnR = PIN_DIST * .8;
|
|
||||||
const pegMargin = PIN_DIST / 8;
|
|
||||||
const plateR = PIN_DIST / 12;
|
|
||||||
|
|
||||||
const pegOffset = pegMargin + pegR;
|
|
||||||
let [x, y] = xy;
|
|
||||||
const left = x - tabSize / 2;
|
|
||||||
const top = y - tabSize / 2;
|
|
||||||
const plateH = 3 * PIN_DIST - tabSize;
|
|
||||||
const plateW = 2 * PIN_DIST + tabSize;
|
|
||||||
const plateL = left;
|
|
||||||
const plateT = top + tabSize;
|
|
||||||
const btnCX = plateL + plateW / 2;
|
|
||||||
const btnCY = plateT + plateH / 2;
|
|
||||||
|
|
||||||
let btng = <SVGGElement>svg.elt("g");
|
|
||||||
//tabs
|
|
||||||
const mkTab = (x: number, y: number) => {
|
|
||||||
svg.child(btng, "rect", { class: "sim-button-tab", x: x, y: y, width: tabSize, height: tabSize})
|
|
||||||
}
|
|
||||||
mkTab(left, top);
|
|
||||||
mkTab(left + 2 * PIN_DIST, top);
|
|
||||||
mkTab(left, top + 3 * PIN_DIST);
|
|
||||||
mkTab(left + 2 * PIN_DIST, top + 3 * PIN_DIST);
|
|
||||||
|
|
||||||
//plate
|
|
||||||
svg.child(btng, "rect", { class: outerCls, x: plateL, y: plateT, rx: plateR, ry: plateR, width: plateW, height: plateH });
|
|
||||||
|
|
||||||
//pegs
|
|
||||||
const mkPeg = (x: number, y: number) => {
|
|
||||||
svg.child(btng, "circle", { class: "sim-button-nut", cx: x, cy: y, r: pegR });
|
|
||||||
}
|
|
||||||
mkPeg(plateL + pegOffset, plateT + pegOffset)
|
|
||||||
mkPeg(plateL + plateW - pegOffset, plateT + pegOffset)
|
|
||||||
mkPeg(plateL + pegOffset, plateT + plateH - pegOffset)
|
|
||||||
mkPeg(plateL + plateW - pegOffset, plateT + plateH - pegOffset)
|
|
||||||
|
|
||||||
//inner btn
|
|
||||||
let innerBtn = svg.child(btng, "circle", { class: innerCls, cx: btnCX, cy: btnCY, r: btnR });
|
|
||||||
|
|
||||||
//return
|
|
||||||
return { el: btng, y: top, x: left, w: plateW, h: plateH + 2 * tabSize };
|
|
||||||
}
|
|
||||||
export const BUTTON_PAIR_STYLE = `
|
|
||||||
.sim-button {
|
|
||||||
pointer-events: none;
|
|
||||||
fill: #000;
|
|
||||||
}
|
|
||||||
.sim-button-outer:active ~ .sim-button,
|
|
||||||
.sim-button-virtual:active {
|
|
||||||
fill: #FFA500;
|
|
||||||
}
|
|
||||||
.sim-button-outer {
|
|
||||||
cursor: pointer;
|
|
||||||
fill: #979797;
|
|
||||||
}
|
|
||||||
.sim-button-outer:hover {
|
|
||||||
stroke:gray;
|
|
||||||
stroke-width: ${PIN_DIST / 5}px;
|
|
||||||
}
|
|
||||||
.sim-button-nut {
|
|
||||||
fill:#000;
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
.sim-button-nut:hover {
|
|
||||||
stroke:${PIN_DIST / 15}px solid #704A4A;
|
|
||||||
}
|
|
||||||
.sim-button-tab {
|
|
||||||
fill:#FFF;
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
.sim-button-virtual {
|
|
||||||
cursor: pointer;
|
|
||||||
fill: rgba(255, 255, 255, 0.6);
|
|
||||||
stroke: rgba(255, 255, 255, 1);
|
|
||||||
stroke-width: ${PIN_DIST / 5}px;
|
|
||||||
}
|
|
||||||
.sim-button-virtual:hover {
|
|
||||||
stroke: rgba(128, 128, 128, 1);
|
|
||||||
}
|
|
||||||
.sim-text-virtual {
|
|
||||||
fill: #000;
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export class ButtonPairView implements IBoardComponent<ButtonPairState> {
|
|
||||||
public element: SVGElement;
|
|
||||||
public defs: SVGElement[];
|
|
||||||
public style = BUTTON_PAIR_STYLE;
|
|
||||||
private state: ButtonPairState;
|
|
||||||
private bus: EventBus;
|
|
||||||
private aBtn: SVGGElement;
|
|
||||||
private bBtn: SVGGElement;
|
|
||||||
private abBtn: SVGGElement;
|
|
||||||
|
|
||||||
public init(bus: EventBus, state: ButtonPairState) {
|
|
||||||
this.state = state;
|
|
||||||
this.bus = bus;
|
|
||||||
this.defs = [];
|
|
||||||
this.element = this.mkBtns();
|
|
||||||
this.updateState();
|
|
||||||
this.attachEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
public moveToCoord(xy: Coord) {
|
|
||||||
let btnWidth = PIN_DIST * 3;
|
|
||||||
let [x, y] = xy;
|
|
||||||
translateEl(this.aBtn, [x, y])
|
|
||||||
translateEl(this.bBtn, [x + btnWidth, y])
|
|
||||||
translateEl(this.abBtn, [x + PIN_DIST * 1.5, y + PIN_DIST * 4])
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateState() {
|
|
||||||
let stateBtns = [this.state.aBtn, this.state.bBtn, this.state.abBtn];
|
|
||||||
let svgBtns = [this.aBtn, this.bBtn, this.abBtn];
|
|
||||||
|
|
||||||
if (this.state.usesButtonAB && this.abBtn.style.visibility != "visible") {
|
|
||||||
this.abBtn.style.visibility = "visible";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateTheme() {}
|
|
||||||
|
|
||||||
private mkBtns() {
|
|
||||||
this.aBtn = mkBtnSvg([0, 0]).el;
|
|
||||||
this.bBtn = mkBtnSvg([0, 0]).el;
|
|
||||||
|
|
||||||
const mkVirtualBtn = () => {
|
|
||||||
const numPins = 2;
|
|
||||||
const w = PIN_DIST * 2.8;
|
|
||||||
const offset = (w - (numPins * PIN_DIST)) / 2;
|
|
||||||
const corner = PIN_DIST / 2;
|
|
||||||
const cx = 0 - offset + w / 2;
|
|
||||||
const cy = cx;
|
|
||||||
const txtSize = PIN_DIST * 1.3;
|
|
||||||
const x = -offset;
|
|
||||||
const y = -offset;
|
|
||||||
const txtXOff = PIN_DIST / 7;
|
|
||||||
const txtYOff = PIN_DIST / 10;
|
|
||||||
|
|
||||||
let btng = <SVGGElement>svg.elt("g");
|
|
||||||
let btn = svg.child(btng, "rect", { class: "sim-button-virtual", x: x, y: y, rx: corner, ry: corner, width: w, height: w});
|
|
||||||
let btnTxt = mkTxt(cx + txtXOff, cy + txtYOff, txtSize, 0, "A+B");
|
|
||||||
svg.addClass(btnTxt, "sim-text")
|
|
||||||
svg.addClass(btnTxt, "sim-text-virtual");
|
|
||||||
btng.appendChild(btnTxt);
|
|
||||||
|
|
||||||
return btng;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.abBtn = mkVirtualBtn();
|
|
||||||
this.abBtn.style.visibility = "hidden";
|
|
||||||
|
|
||||||
let el = svg.elt("g");
|
|
||||||
svg.addClass(el, "sim-buttonpair")
|
|
||||||
el.appendChild(this.aBtn);
|
|
||||||
el.appendChild(this.bBtn);
|
|
||||||
el.appendChild(this.abBtn);
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
private attachEvents() {
|
|
||||||
let btnStates = [this.state.aBtn, this.state.bBtn];
|
|
||||||
let btnSvgs = [this.aBtn, this.bBtn];
|
|
||||||
btnSvgs.forEach((btn, index) => {
|
|
||||||
btn.addEventListener(pointerEvents.down, ev => {
|
|
||||||
btnStates[index].pressed = true;
|
|
||||||
})
|
|
||||||
btn.addEventListener(pointerEvents.leave, ev => {
|
|
||||||
btnStates[index].pressed = false;
|
|
||||||
})
|
|
||||||
btn.addEventListener(pointerEvents.up, ev => {
|
|
||||||
btnStates[index].pressed = false;
|
|
||||||
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_UP);
|
|
||||||
this.bus.queue(btnStates[index].id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
let updateBtns = (s: boolean) => {
|
|
||||||
btnStates.forEach(b => b.pressed = s)
|
|
||||||
};
|
|
||||||
this.abBtn.addEventListener(pointerEvents.down, ev => {
|
|
||||||
updateBtns(true);
|
|
||||||
})
|
|
||||||
this.abBtn.addEventListener(pointerEvents.leave, ev => {
|
|
||||||
updateBtns(false);
|
|
||||||
})
|
|
||||||
this.abBtn.addEventListener(pointerEvents.up, ev => {
|
|
||||||
updateBtns(false);
|
|
||||||
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_UP);
|
|
||||||
this.bus.queue(this.state.abBtn.id, DAL.MICROBIT_BUTTON_EVT_CLICK);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,306 +0,0 @@
|
|||||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
|
||||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
|
||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
|
||||||
|
|
||||||
namespace pxsim.visuals {
|
|
||||||
export const BOARD_SYTLE = `
|
|
||||||
.noselect {
|
|
||||||
-webkit-touch-callout: none; /* iOS Safari */
|
|
||||||
-webkit-user-select: none; /* Chrome/Safari/Opera */
|
|
||||||
-khtml-user-select: none; /* Konqueror */
|
|
||||||
-moz-user-select: none; /* Firefox */
|
|
||||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
|
||||||
user-select: none; /* Non-prefixed version, currently
|
|
||||||
not supported by any browser */
|
|
||||||
}
|
|
||||||
|
|
||||||
.sim-board-pin {
|
|
||||||
fill:#999;
|
|
||||||
stroke:#000;
|
|
||||||
stroke-width:${PIN_DIST / 3.0}px;
|
|
||||||
}
|
|
||||||
.sim-board-pin-lbl {
|
|
||||||
fill: #333;
|
|
||||||
}
|
|
||||||
.gray-cover {
|
|
||||||
fill:#FFF;
|
|
||||||
opacity: 0.7;
|
|
||||||
stroke-width:0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.sim-board-pin-hover {
|
|
||||||
visibility: hidden;
|
|
||||||
pointer-events: all;
|
|
||||||
stroke-width:${PIN_DIST / 6.0}px;
|
|
||||||
}
|
|
||||||
.sim-board-pin-hover:hover {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.sim-board-pin-lbl {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.sim-board-outline .sim-board-pin-lbl {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.sim-board-pin-lbl {
|
|
||||||
fill: #555;
|
|
||||||
}
|
|
||||||
.sim-board-pin-lbl-hover {
|
|
||||||
fill: red;
|
|
||||||
}
|
|
||||||
.sim-board-outline .sim-board-pin-lbl-hover {
|
|
||||||
fill: black;
|
|
||||||
}
|
|
||||||
.sim-board-pin-lbl,
|
|
||||||
.sim-board-pin-lbl-hover {
|
|
||||||
font-family:"Lucida Console", Monaco, monospace;
|
|
||||||
pointer-events: all;
|
|
||||||
stroke-width: 0;
|
|
||||||
}
|
|
||||||
.sim-board-pin-lbl-hover {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.sim-board-outline .sim-board-pin-hover:hover + .sim-board-pin-lbl,
|
|
||||||
.sim-board-pin-lbl.highlight {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.sim-board-outline .sim-board-pin-hover:hover + * + .sim-board-pin-lbl-hover,
|
|
||||||
.sim-board-pin-lbl-hover.highlight {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
/* Graying out */
|
|
||||||
.grayed .sim-board-pin-lbl:not(.highlight) {
|
|
||||||
fill: #AAA;
|
|
||||||
}
|
|
||||||
.grayed .sim-board-pin:not(.highlight) {
|
|
||||||
fill:#BBB;
|
|
||||||
stroke:#777;
|
|
||||||
}
|
|
||||||
.grayed .gray-cover {
|
|
||||||
visibility: inherit;
|
|
||||||
}
|
|
||||||
.grayed .sim-cmp:not(.notgrayed) {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
/* Highlighting */
|
|
||||||
.sim-board-pin-lbl.highlight {
|
|
||||||
fill: #000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.sim-board-pin.highlight {
|
|
||||||
fill:#999;
|
|
||||||
stroke:#000;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const PIN_LBL_SIZE = PIN_DIST * 0.7;
|
|
||||||
const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
|
|
||||||
const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
|
|
||||||
const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
|
|
||||||
|
|
||||||
export interface GenericBoardProps {
|
|
||||||
visualDef: BoardImageDefinition;
|
|
||||||
wireframe?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextBoardId = 0;
|
|
||||||
export class GenericBoardSvg implements BoardView {
|
|
||||||
private element: SVGSVGElement;
|
|
||||||
private style: SVGStyleElement;
|
|
||||||
private defs: SVGDefsElement;
|
|
||||||
private g: SVGGElement;
|
|
||||||
private background: SVGElement;
|
|
||||||
private width: number;
|
|
||||||
private height: number;
|
|
||||||
private id: number;
|
|
||||||
|
|
||||||
// pins & labels
|
|
||||||
//(truth)
|
|
||||||
private allPins: GridPin[] = [];
|
|
||||||
private allLabels: GridLabel[] = [];
|
|
||||||
//(cache)
|
|
||||||
private pinNmToLbl: Map<GridLabel> = {};
|
|
||||||
private pinNmToPin: Map<GridPin> = {};
|
|
||||||
|
|
||||||
constructor(public props: GenericBoardProps) {
|
|
||||||
//TODO: handle wireframe mode
|
|
||||||
this.id = nextBoardId++;
|
|
||||||
let visDef = props.visualDef;
|
|
||||||
let imgHref = props.wireframe ? visDef.outlineImage : visDef.image;
|
|
||||||
let boardImgAndSize = mkImageSVG({
|
|
||||||
image: imgHref,
|
|
||||||
width: visDef.width,
|
|
||||||
height: visDef.height,
|
|
||||||
imageUnitDist: visDef.pinDist,
|
|
||||||
targetUnitDist: PIN_DIST
|
|
||||||
});
|
|
||||||
let scaleFn = mkScaleFn(visDef.pinDist, PIN_DIST);
|
|
||||||
this.width = boardImgAndSize.w;
|
|
||||||
this.height = boardImgAndSize.h;
|
|
||||||
let img = boardImgAndSize.el;
|
|
||||||
this.element = <SVGSVGElement>svg.elt("svg");
|
|
||||||
svg.hydrate(this.element, {
|
|
||||||
"version": "1.0",
|
|
||||||
"viewBox": `0 0 ${this.width} ${this.height}`,
|
|
||||||
"class": `sim sim-board-id-${this.id}`,
|
|
||||||
"x": "0px",
|
|
||||||
"y": "0px"
|
|
||||||
});
|
|
||||||
if (props.wireframe)
|
|
||||||
svg.addClass(this.element, "sim-board-outline")
|
|
||||||
this.style = <SVGStyleElement>svg.child(this.element, "style", {});
|
|
||||||
this.style.textContent += BOARD_SYTLE;
|
|
||||||
this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
|
|
||||||
this.g = <SVGGElement>svg.elt("g");
|
|
||||||
this.element.appendChild(this.g);
|
|
||||||
|
|
||||||
// main board
|
|
||||||
this.g.appendChild(img);
|
|
||||||
this.background = img;
|
|
||||||
svg.hydrate(img, { class: "sim-board" });
|
|
||||||
let backgroundCover = this.mkGrayCover(0, 0, this.width, this.height);
|
|
||||||
this.g.appendChild(backgroundCover);
|
|
||||||
|
|
||||||
// ----- pins
|
|
||||||
const mkSquarePin = (): SVGElAndSize => {
|
|
||||||
let el = svg.elt("rect");
|
|
||||||
let width = SQUARE_PIN_WIDTH;
|
|
||||||
svg.hydrate(el, {
|
|
||||||
class: "sim-board-pin",
|
|
||||||
width: width,
|
|
||||||
height: width,
|
|
||||||
});
|
|
||||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
|
||||||
}
|
|
||||||
const mkSquareHoverPin = (): SVGElAndSize => {
|
|
||||||
let el = svg.elt("rect");
|
|
||||||
let width = SQUARE_PIN_HOVER_WIDTH;
|
|
||||||
svg.hydrate(el, {
|
|
||||||
class: "sim-board-pin-hover",
|
|
||||||
width: width,
|
|
||||||
height: width
|
|
||||||
});
|
|
||||||
return {el: el, w: width, h: width, x: 0, y: 0};
|
|
||||||
}
|
|
||||||
const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
|
|
||||||
let xOffset = scaleFn(pinBlock.x) + PIN_DIST / 2.0;
|
|
||||||
let yOffset = scaleFn(pinBlock.y) + PIN_DIST / 2.0;
|
|
||||||
let rowCount = 1;
|
|
||||||
let colCount = pinBlock.labels.length;
|
|
||||||
let getColName = (colIdx: number) => pinBlock.labels[colIdx];
|
|
||||||
let getRowName = () => `${blockIdx + 1}`
|
|
||||||
let getGroupName = () => pinBlock.labels.join(" ");
|
|
||||||
let gridRes = mkGrid({
|
|
||||||
xOffset: xOffset,
|
|
||||||
yOffset: yOffset,
|
|
||||||
rowCount: rowCount,
|
|
||||||
colCount: colCount,
|
|
||||||
pinDist: PIN_DIST,
|
|
||||||
mkPin: mkSquarePin,
|
|
||||||
mkHoverPin: mkSquareHoverPin,
|
|
||||||
getRowName: getRowName,
|
|
||||||
getColName: getColName,
|
|
||||||
getGroupName: getGroupName,
|
|
||||||
});
|
|
||||||
let pins = gridRes.allPins;
|
|
||||||
let pinsG = gridRes.g;
|
|
||||||
svg.addClass(gridRes.g, "sim-board-pin-group");
|
|
||||||
return gridRes;
|
|
||||||
};
|
|
||||||
let pinBlocks = visDef.pinBlocks.map(mkPinBlockGrid);
|
|
||||||
let pinToBlockDef: PinBlockDefinition[] = [];
|
|
||||||
pinBlocks.forEach((blk, blkIdx) => blk.allPins.forEach((p, pIdx) => {
|
|
||||||
this.allPins.push(p);
|
|
||||||
pinToBlockDef.push(visDef.pinBlocks[blkIdx]);
|
|
||||||
}));
|
|
||||||
//tooltip
|
|
||||||
this.allPins.forEach(p => {
|
|
||||||
let tooltip = p.col;
|
|
||||||
svg.hydrate(p.el, {title: tooltip});
|
|
||||||
svg.hydrate(p.hoverEl, {title: tooltip});
|
|
||||||
});
|
|
||||||
//attach pins
|
|
||||||
this.allPins.forEach(p => {
|
|
||||||
this.g.appendChild(p.el);
|
|
||||||
this.g.appendChild(p.hoverEl);
|
|
||||||
});
|
|
||||||
//catalog pins
|
|
||||||
this.allPins.forEach(p => {
|
|
||||||
this.pinNmToPin[p.col] = p;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ----- labels
|
|
||||||
const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string, pos: "above" | "below"): SVGTextElement => {
|
|
||||||
//TODO: extract constants
|
|
||||||
let lblY: number;
|
|
||||||
let lblX: number;
|
|
||||||
|
|
||||||
if (pos === "below") {
|
|
||||||
let lblLen = size * 0.25 * txt.length;
|
|
||||||
lblX = pinX;
|
|
||||||
lblY = pinY + 12 + lblLen;
|
|
||||||
} else {
|
|
||||||
let lblLen = size * 0.32 * txt.length;
|
|
||||||
lblX = pinX;
|
|
||||||
lblY = pinY - 11 - lblLen;
|
|
||||||
}
|
|
||||||
let el = mkTxt(lblX, lblY, size, -90, txt);
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
const mkLabel = (pinX: number, pinY: number, txt: string, pos: "above" | "below"): GridLabel => {
|
|
||||||
let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt, pos);
|
|
||||||
svg.addClass(el, "sim-board-pin-lbl");
|
|
||||||
let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt, pos);
|
|
||||||
svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
|
|
||||||
let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
this.allLabels = this.allPins.map((p, pIdx) => {
|
|
||||||
let blk = pinToBlockDef[pIdx];
|
|
||||||
return mkLabel(p.cx, p.cy, p.col, blk.labelPosition);
|
|
||||||
});
|
|
||||||
//attach labels
|
|
||||||
this.allLabels.forEach(l => {
|
|
||||||
this.g.appendChild(l.el);
|
|
||||||
this.g.appendChild(l.hoverEl);
|
|
||||||
});
|
|
||||||
//catalog labels
|
|
||||||
this.allPins.forEach((pin, pinIdx) => {
|
|
||||||
let lbl = this.allLabels[pinIdx];
|
|
||||||
this.pinNmToLbl[pin.col] = lbl;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCoord(pinNm: string): Coord {
|
|
||||||
let pin = this.pinNmToPin[pinNm];
|
|
||||||
if (!pin)
|
|
||||||
return null;
|
|
||||||
return [pin.cx, pin.cy];
|
|
||||||
}
|
|
||||||
|
|
||||||
private mkGrayCover(x: number, y: number, w: number, h: number) {
|
|
||||||
let rect = <SVGRectElement>svg.elt("rect");
|
|
||||||
svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public getView(): SVGAndSize<SVGSVGElement> {
|
|
||||||
return {el: this.element, w: this.width, h: this.height, x: 0, y: 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPinDist() {
|
|
||||||
return PIN_DIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
public highlightPin(pinNm: string) {
|
|
||||||
let lbl = this.pinNmToLbl[pinNm];
|
|
||||||
let pin = this.pinNmToPin[pinNm];
|
|
||||||
if (lbl && pin) {
|
|
||||||
svg.addClass(lbl.el, "highlight");
|
|
||||||
svg.addClass(lbl.hoverEl, "highlight");
|
|
||||||
svg.addClass(pin.el, "highlight");
|
|
||||||
svg.addClass(pin.hoverEl, "highlight");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
namespace pxsim.visuals {
|
|
||||||
export function mkGenericPartSVG(partVisual: PartVisualDefinition): SVGAndSize<SVGImageElement> {
|
|
||||||
let imgAndSize = mkImageSVG({
|
|
||||||
image: partVisual.image,
|
|
||||||
width: partVisual.width,
|
|
||||||
height: partVisual.height,
|
|
||||||
imageUnitDist: partVisual.pinDist,
|
|
||||||
targetUnitDist: PIN_DIST
|
|
||||||
});
|
|
||||||
return imgAndSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GenericPart implements IBoardComponent<any> {
|
|
||||||
public style: string = "";
|
|
||||||
public element: SVGElement;
|
|
||||||
defs: SVGElement[] = [];
|
|
||||||
|
|
||||||
constructor(partVisual: PartVisualDefinition) {
|
|
||||||
let imgAndSize = mkGenericPartSVG(partVisual);
|
|
||||||
let img = imgAndSize.el;
|
|
||||||
let scaleFn = mkScaleFn(partVisual.pinDist, PIN_DIST);
|
|
||||||
let [pinX, pinY] = partVisual.firstPin;
|
|
||||||
let left = -scaleFn(pinX);
|
|
||||||
let top = -scaleFn(pinY);
|
|
||||||
translateEl(img, [left, top]); // So that 0,0 is on the first pin
|
|
||||||
this.element = svg.elt("g");
|
|
||||||
this.element.appendChild(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
moveToCoord(xy: Coord): void {
|
|
||||||
translateEl(this.element, xy);
|
|
||||||
}
|
|
||||||
|
|
||||||
//unused
|
|
||||||
init(bus: EventBus, state: any, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void { }
|
|
||||||
updateState(): void { }
|
|
||||||
updateTheme(): void { }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
|
||||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
|
||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
|
||||||
|
|
||||||
namespace pxsim.visuals {
|
namespace pxsim.visuals {
|
||||||
export function mkLedMatrixSvg(xy: Coord, rows: number, cols: number):
|
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 background: SVGElement;
|
||||||
private ledsOuter: SVGElement[];
|
private ledsOuter: SVGElement[];
|
||||||
private leds: SVGElement[];
|
private leds: SVGElement[];
|
||||||
|
@ -226,18 +226,6 @@ namespace pxsim.visuals {
|
|||||||
wireframe?: boolean;
|
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 {
|
export class MicrobitBoardSvg implements BoardView {
|
||||||
public element: SVGSVGElement;
|
public element: SVGSVGElement;
|
||||||
private style: SVGStyleElement;
|
private style: SVGStyleElement;
|
@ -2,45 +2,6 @@
|
|||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
/// <reference path="../../libs/microbit/dal.d.ts"/>
|
||||||
/// <reference path="../../libs/microbit/shims.d.ts"/>
|
/// <reference path="../../libs/microbit/shims.d.ts"/>
|
||||||
/// <reference path="../../libs/microbit/enums.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 {
|
namespace pxsim.visuals {
|
||||||
const PIXEL_SPACING = PIN_DIST * 3;
|
const PIXEL_SPACING = PIN_DIST * 3;
|
||||||
@ -107,21 +68,21 @@ namespace pxsim.visuals {
|
|||||||
public cy: number;
|
public cy: number;
|
||||||
|
|
||||||
constructor(xy: Coord = [0, 0]) {
|
constructor(xy: Coord = [0, 0]) {
|
||||||
let circle = <SVGElement>svg.elt("rect");
|
let el = <SVGElement>svg.elt("rect");
|
||||||
let r = PIXEL_RADIUS;
|
let r = PIXEL_RADIUS;
|
||||||
let [cx, cy] = xy;
|
let [cx, cy] = xy;
|
||||||
let y = cy - r;
|
let y = cy - r;
|
||||||
let x = 0;
|
svg.hydrate(el, { x: "-50%", y: y, width: "100%", height: r * 2, class: "sim-neopixel" });
|
||||||
svg.hydrate(circle, { x: "-50%", y: y, width: "100%", height: r*2, class: "sim-neopixel" });
|
this.el = el;
|
||||||
this.el = circle;
|
|
||||||
this.cy = cy;
|
this.cy = cy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setRgb(rgb: [number, number, number]) {
|
public setRgb(rgb: [number, number, number]) {
|
||||||
let hsl = rgbToHsl(rgb);
|
let hsl = visuals.rgbToHsl(rgb);
|
||||||
let [h, s, l] = hsl;
|
let [h, s, l] = hsl;
|
||||||
//We ignore luminosity since it doesn't map well to real-life brightness
|
// at least 70% luminosity
|
||||||
let fill = `hsl(${h}, ${s}%, 70%)`;
|
l = Math.max(l, 60);
|
||||||
|
let fill = `hsl(${h}, ${s}%, ${l}%)`;
|
||||||
this.el.setAttribute("fill", fill);
|
this.el.setAttribute("fill", fill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,29 +153,26 @@ namespace pxsim.visuals {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function gpioPinToPinNumber(gpioPin: string): number {
|
function digitalPinToPinNumber(gpioPin: string): number {
|
||||||
let pinNumStr = gpioPin.split("P")[1];
|
const MICROBIT_ID_IO_P0 = 7; //TODO: don't hardcode this, import enums.d.ts
|
||||||
let pinNum = Number(pinNumStr) + 7 /*MICROBIT_ID_IO_P0; 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
|
return pinNum
|
||||||
}
|
}
|
||||||
function parseNeoPixelMode(modeStr: string): NeoPixelMode {
|
function parseNeoPixelMode(modeStr: string): NeoPixelMode {
|
||||||
const modeMap: Map<NeoPixelMode> = {
|
const modeMap: Map<NeoPixelMode> = {
|
||||||
"NeoPixelMode.RGB": NeoPixelMode.RGB,
|
"NeoPixelMode.RGB": NeoPixelMode.RGB,
|
||||||
"NeoPixelMode.RGBW": NeoPixelMode.RGBW,
|
"NeoPixelMode.RGBW": NeoPixelMode.RGBW
|
||||||
"*": NeoPixelMode.RGB,
|
|
||||||
};
|
};
|
||||||
let mode: NeoPixelMode = null;
|
return modeMap[modeStr] || NeoPixelMode.RGB;
|
||||||
for (let key in modeMap) {
|
|
||||||
if (key == modeStr) {
|
|
||||||
mode = modeMap[key];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
U.assert(mode != null, "Unknown NeoPixelMode: " + modeStr);
|
|
||||||
return mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NeoPixelView implements IBoardComponent<NeoPixelState> {
|
export class NeoPixelView implements IBoardPart<NeoPixelState> {
|
||||||
public style: string = `
|
public style: string = `
|
||||||
.sim-neopixel-canvas {
|
.sim-neopixel-canvas {
|
||||||
}
|
}
|
||||||
@ -232,6 +190,7 @@ namespace pxsim.visuals {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
public element: SVGElement;
|
public element: SVGElement;
|
||||||
|
public overElement: SVGElement;
|
||||||
public defs: SVGElement[];
|
public defs: SVGElement[];
|
||||||
private state: NeoPixelState;
|
private state: NeoPixelState;
|
||||||
private canvas: NeoPixelCanvas;
|
private canvas: NeoPixelCanvas;
|
||||||
@ -241,22 +200,24 @@ namespace pxsim.visuals {
|
|||||||
private pin: number;
|
private pin: number;
|
||||||
private mode: NeoPixelMode;
|
private mode: NeoPixelMode;
|
||||||
|
|
||||||
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, gpioPins: string[], otherArgs: string[]): void {
|
public init(bus: EventBus, state: NeoPixelState, svgEl: SVGSVGElement, otherParams: Map<string>): void {
|
||||||
U.assert(otherArgs.length === 1, "NeoPixels assumes a RGB vs RGBW mode is passed to it");
|
U.assert(!!otherParams["mode"], "NeoPixels assumes a RGB vs RGBW mode is passed to it");
|
||||||
let modeStr = otherArgs[0];
|
U.assert(!!otherParams["pin"], "NeoPixels assumes a pin is passed to it");
|
||||||
|
let modeStr = otherParams["mode"];
|
||||||
this.mode = parseNeoPixelMode(modeStr);
|
this.mode = parseNeoPixelMode(modeStr);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.stripGroup = <SVGGElement>svg.elt("g");
|
this.stripGroup = <SVGGElement>svg.elt("g");
|
||||||
this.element = this.stripGroup;
|
this.element = this.stripGroup;
|
||||||
let pinStr = gpioPins[0];
|
let pinStr = otherParams["pin"];
|
||||||
this.pin = gpioPinToPinNumber(pinStr);
|
this.pin = digitalPinToPinNumber(pinStr);
|
||||||
this.lastLocation = [0, 0];
|
this.lastLocation = [0, 0];
|
||||||
let part = mkNeoPixelPart();
|
let part = mkNeoPixelPart();
|
||||||
this.part = part;
|
this.part = part;
|
||||||
this.stripGroup.appendChild(part.el);
|
this.stripGroup.appendChild(part.el);
|
||||||
let canvas = new NeoPixelCanvas(this.pin);
|
let canvas = new NeoPixelCanvas(this.pin);
|
||||||
this.canvas = canvas;
|
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);
|
canvasG.appendChild(canvas.canvas);
|
||||||
this.updateStripLoc();
|
this.updateStripLoc();
|
||||||
}
|
}
|
||||||
@ -268,6 +229,7 @@ namespace pxsim.visuals {
|
|||||||
}
|
}
|
||||||
private updateStripLoc() {
|
private updateStripLoc() {
|
||||||
let [x, y] = this.lastLocation;
|
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]);
|
this.canvas.setLoc([x + CANVAS_LEFT, y + CANVAS_TOP]);
|
||||||
svg.hydrate(this.part.el, { transform: `translate(${x} ${y})` }); //TODO: update part's l,h, etc.
|
svg.hydrate(this.part.el, { transform: `translate(${x} ${y})` }); //TODO: update part's l,h, etc.
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|