A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

545 行
14KB

  1. import {EventCaller} from "/app/js/common/EventCaller.js";
  2. import Utils from "/app/js/common/Utils.js";
  3. // Keycode list based on...
  4. // https://keycode.info/
  5. var KEYBYCODE = {
  6. 3:"break",
  7. 8:"backspace",
  8. 9:"tab",
  9. 13:"enter",
  10. 16:"shift",
  11. 17:"ctrl",
  12. 18:"alt",
  13. 19:"pause",
  14. 20:"capslock",
  15. 27:"esc",
  16. 32:"space",
  17. 33:"pageup",
  18. 34:"pagedown",
  19. 35:"end",
  20. 36:"home",
  21. 37:"left",
  22. 38:"up",
  23. 39:"right",
  24. 40:"down",
  25. 41:"select",
  26. 42:"print",
  27. 43:"execute",
  28. 44:"printscreen",
  29. 45:"insert",
  30. 46:"delete",
  31. 47:"help",
  32. 48:"0",
  33. 49:"1",
  34. 50:"2",
  35. 51:"3",
  36. 52:"4",
  37. 53:"5",
  38. 54:"6",
  39. 55:"7",
  40. 56:"8",
  41. 57:"9",
  42. 65:"a",
  43. 66:"b",
  44. 67:"c",
  45. 68:"d",
  46. 69:"e",
  47. 70:"f",
  48. 71:"g",
  49. 72:"h",
  50. 73:"i",
  51. 74:"j",
  52. 75:"k",
  53. 76:"l",
  54. 77:"m",
  55. 78:"n",
  56. 79:"o",
  57. 80:"p",
  58. 81:"q",
  59. 82:"r",
  60. 83:"s",
  61. 84:"t",
  62. 85:"u",
  63. 86:"v",
  64. 87:"w",
  65. 88:"x",
  66. 89:"y",
  67. 90:"z",
  68. 91:"leftmod", // Window key (left)
  69. 92:"rightwin",// Window key (right)
  70. 93:"rightmod",// Window key (right)
  71. 96:"num0",
  72. 97:"num1",
  73. 98:"num2",
  74. 99:"num3",
  75. 100:"num4",
  76. 101:"num5",
  77. 102:"num6",
  78. 103:"num7",
  79. 104:"num8",
  80. 105:"num9",
  81. 112:"f1",
  82. 113:"f2",
  83. 114:"f3",
  84. 115:"f4",
  85. 116:"f5",
  86. 117:"f6",
  87. 118:"f7",
  88. 119:"f8",
  89. 120:"f9",
  90. 121:"f10",
  91. 122:"f11",
  92. 123:"f12",
  93. 144:"numlock",
  94. 145:"scrolllock",
  95. };
  96. var KEYBYNAME = (function(){
  97. var keys = Object.keys(KEYBYCODE);
  98. var o = {};
  99. for (var i=0; i < keys.length; i++){
  100. if (KEYBYCODE.hasOwnProperty(keys[i])){
  101. o[KEYBYCODE[keys[i]]] = keys[i];
  102. }
  103. }
  104. return o;
  105. })();
  106. var KEYTYPE = {
  107. "number":[48,49,50,51,52,53,54,55,56,57,96,97,98,99,100,101,102,103,104,105],
  108. "letter":[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90],
  109. "mod":[16,17,18],
  110. "arrow":[37,38,39,40],
  111. "wasd":[87,65,83,68],
  112. "fn":[112,113,114,115,116,117,118,119,120,121,122,123],
  113. "n1":[48,96],
  114. "n2":[49,97],
  115. "n3":[50,98],
  116. "n4":[51,99],
  117. "n5":[52,100],
  118. "n6":[53,101],
  119. "n7":[54,102],
  120. "n8":[55,103],
  121. "n9":[56,104],
  122. "n0":[57,105]
  123. };
  124. var KEYMAP = {
  125. "lastcode":null,
  126. "lastaction":"",
  127. "currentcodes":[]
  128. };
  129. // TODO: Reeval this idea.
  130. const KEYPRESS_DELAY = 350; // Time in milliseconds. NOTE: May make this a variable in future.
  131. const MOUSECLICK_DELAY = 350; // Time in milliseconds.
  132. function AssignCodeName(code, name){
  133. name = name.toLowerCase();
  134. var remove = (code in Object.keys(KEYBYCODE));
  135. if (name in Object.keys(KEYBYNAME)){
  136. if (remove && KEYBYCODE[code] === name){
  137. return; // We're being asked to replace the exact same thing. SKIP!
  138. }
  139. throw new ValueError("Key name '" + name + "' already assigned. Cannot use duplicate key names.");
  140. }
  141. if (remove){
  142. delete KEYBYNAME[KEYBYCODE[code]];
  143. }
  144. KEYBYCODE[code] = name;
  145. KEYBYNAME[name] = code;
  146. }
  147. function KeymapContains(code){
  148. return KEYMAP["currentcodes"].findIndex(c=>c[0] == code) >= 0;
  149. }
  150. function AddToKeymap(code, action){
  151. KEYMAP["lastcode"] = code;
  152. KEYMAP["lastaction"] = action;
  153. if (KeymapContains(code) == false){
  154. KEYMAP["currentcodes"].push([code, Math.floor(Date.now())]);
  155. if (KEYMAP["currentcodes"].length > 1){
  156. KEYMAP["currentcodes"].sort(function(a, b){return a[0] - b[0];});
  157. }
  158. return true;
  159. }
  160. return false;
  161. }
  162. function RemoveFromKeymap(code, action){
  163. KEYMAP["lastcode"] = code;
  164. KEYMAP["lastaction"] = action;
  165. var ctime = Math.floor(Date.now());
  166. for (var i=0; i < KEYMAP["currentcodes"].length; i++){
  167. if (KEYMAP["currentcodes"][i][0] === code){
  168. var timediff = ctime - KEYMAP["currentcodes"][i][1];
  169. KEYMAP["currentcodes"].splice(i, 1);
  170. return timediff;
  171. }
  172. }
  173. return -1;
  174. }
  175. function KeyNameToCode(key){
  176. return (key in Object.keys(KEYBYNAME)) ? KEYBYNAME[key] : -1;
  177. }
  178. function CodeToKeyName(code){
  179. return (code in Object.keys(KEYBYCODE)) ? KEYBYCODE[code] : "" + code;
  180. }
  181. function CodesToEventName(codes){
  182. var ename = "";
  183. for (var i=0; i < codes.length; i++){
  184. ename += ((ename !== "") ? "+" : "") + CodeToKeyName(codes[i]);
  185. }
  186. return ename;
  187. }
  188. function KeymapEventName(){
  189. return CodesToEventName(KEYMAP["currentcodes"].map(e=>e[0]));
  190. }
  191. function ReorderEventName(ename){
  192. // This function takes a keyboard event name and reorders it into key-code order.
  193. // This way users can write the event any way they want, but should still result in proper
  194. // event being called.
  195. var elist = ename.split("+");
  196. var ecodes = [];
  197. for (var i=0; i < elist.length; i++){
  198. var key = elist[i].trim().toLowerCase();
  199. if (!(key in Object.keys(KEYBYNAME))){
  200. if (!Number.isNaN(key))
  201. ecodes.push(parseInt(key));
  202. else
  203. return ""; // This event name does not include valid key name!
  204. } else {
  205. ecodes.push(KEYBYNAME[key]);
  206. }
  207. }
  208. if (ecodes.length > 0){
  209. ecodes.sort(function(a, b){return a-b;});
  210. return CodesToEventName(ecodes);
  211. }
  212. return "";
  213. }
  214. export default class Input{
  215. constructor(){
  216. this.__emitter = new EventCaller();
  217. this.__preventDefaults = false;
  218. // Internet Explorer... that fudged up p.o.s. has to make mouse button detection difficult
  219. // with different button values from ALL other browsers. So... if Input has this value set to
  220. // true, then mouseup and mousedown events will base it's detection of IE button values, instead
  221. // of the real values.
  222. this.__ieMouseMode = false;
  223. // If set, this is the element that the mouse will focus on and adjust it's position against.
  224. this.__mouseTarget = null;
  225. this.__mousePosition = null;
  226. this.__mouseLastButton = -1;
  227. this.__mouseLastAction = "";
  228. this.__mouseButtons = [];
  229. this.enableKeyboardInput = (function(){
  230. var handle_keydown = (function(e){
  231. if (AddToKeymap(e.keyCode, "keydown")){
  232. var ename = KeymapEventName();
  233. var edata = {
  234. source: this,
  235. iscombo: (ename.indexOf("+") >= 0),
  236. keys: ename,
  237. keycode:e.keyCode,
  238. keyname:CodeToKeyName(e.keyCode),
  239. action:"keydown"
  240. }
  241. this.__emitter.emit(ename, edata);
  242. this.__emitter.emit("keydown", edata);
  243. }
  244. }).bind(this);
  245. var handle_keyup = (function(e){
  246. var timediff = RemoveFromKeymap(e.keyCode, "keyup");
  247. if (timediff < 0){
  248. console.log("WARNING: Failed to find keycode '" + e.keyCode + "' in the Key Map.");
  249. } else {
  250. var ename = KeymapEventName();
  251. var edata = {
  252. source: this,
  253. iscombo: (ename.indexOf("+") >= 0),
  254. keys: ename,
  255. keycode: e.keyCode,
  256. keyname: CodeToKeyName(e.keyCode),
  257. action:"keyup"
  258. }
  259. if (timediff <= KEYPRESS_DELAY && KEYMAP["currentcodes"].length <= 0){
  260. this.__emitter.emit("keypress", edata);
  261. }
  262. this.__emitter.emit("keyup", edata);
  263. }
  264. }).bind(this);
  265. return (function(enable){
  266. enable = (enable !== false);
  267. if (enable){
  268. window.addEventListener("keydown", handle_keydown, false);
  269. //window.addEventListener("keypress", handle_keypress, false);
  270. window.addEventListener("keyup", handle_keyup, false);
  271. } else {
  272. window.removeEventListener("keydown", handle_keydown);
  273. window.removeEventListener("keyup", handle_keyup);
  274. }
  275. }).bind(this);
  276. }).apply(this);
  277. this.enableMouseInput = (function(){
  278. var mousePosition = (function(e){
  279. var pos = {
  280. lastX: (this.__mousePosition !== null) ? this.__mousePosition.x : null,
  281. lastY: (this.__mousePosition !== null) ? this.__mousePosition.y : null,
  282. x: e.clientX,
  283. y: e.clientY,
  284. inbounds: true
  285. }
  286. if (this.__mouseTarget !== null){
  287. var rect = this.__mouseTarget.getBoundingClientRect();
  288. pos.x -= rect.left;
  289. pos.y -= rect.top;
  290. pos.inbounds = (pos.x >= 0 && pos.x < rect.right && pos.y >= 0 && pos.y < rect.bottom);
  291. }
  292. return pos;
  293. }).bind(this);
  294. var buttonID = (function(e){
  295. var btn = e.button;
  296. if ((this.__ieMouseMode && btn === 1) || (!this.__ieMouseMode && btn === 0)){
  297. btn = 0;
  298. } else if ((this.__ieMouseMode && e.button === 4) || (!this.__ieMouseMode && e.button === 1)){
  299. btn = 1;
  300. }
  301. return btn;
  302. }).bind(this);
  303. var addMouseButton = (function(btn){
  304. if (this.__mouseButtons.findIndex(b=>b[0]===btn) < 0){
  305. this.__mouseButtons.push([btn, Math.floor(Date.now())]);
  306. return true;
  307. }
  308. return false;
  309. }).bind(this);
  310. var removeMouseButton = (function(btn){
  311. var idx = this.__mouseButtons.findIndex(b=>b[0]===btn);
  312. var diff = -1;
  313. if (idx >= 0){
  314. diff = Math.floor(Date.now()) - this.__mouseButtons[idx][1];
  315. this.__mouseButtons.splice(idx, 1);
  316. }
  317. return diff;
  318. }).bind(this);
  319. var handle_mousemove = (function(e){
  320. if (this.__preventDefaults)
  321. e.preventDefault();
  322. var pos = mousePosition(e);
  323. if (pos.inbounds){
  324. this.__mousePosition = pos;
  325. this.__mouseLastAction = "mousemove";
  326. this.__emitter.emit("mousemove", {
  327. source: this,
  328. lastX: pos.lastX,
  329. lastY: pos.lastY,
  330. x: pos.x,
  331. y: pos.y,
  332. button: this.__mouseLastButton,
  333. action: "mousemove"
  334. });
  335. }
  336. }).bind(this);
  337. var handle_mousedown = (function(e){
  338. var button = buttonID(e);
  339. var pos = mousePosition(e);
  340. if (pos.inbounds){
  341. if (this.__preventDefaults)
  342. e.preventDefault();
  343. if (addMouseButton(button)){
  344. this.__mousePosition = pos;
  345. this.__mouseLastButton = button;
  346. this.__mouseLastAction = "mousedown";
  347. this.__emitter.emit("mousedown", {
  348. source: this,
  349. lastX: pos.lastX,
  350. lastY: pos.lastY,
  351. x: pos.x,
  352. y: pos.y,
  353. button: button,
  354. action: "mousedown"
  355. });
  356. }
  357. }
  358. }).bind(this);
  359. var handle_mouseup = (function(e){
  360. if (this.__preventDefaults)
  361. e.preventDefault();
  362. var button = buttonID(e);
  363. var pos = mousePosition(e);
  364. // NOTE: I still want to check for button removal, even before testing if an event should
  365. // fire, so that I don't have any phantom buttons listed as "pressed" in the mouseButtons list.
  366. var diff = removeMouseButton(button);
  367. if (pos.inbounds && diff >= 0){
  368. this.__mousePosition = pos;
  369. this.__mouseLastButton = button;
  370. this.__mouseLastAction = "mouseup";
  371. this.__emitter.emit("mouseup", {
  372. source: this,
  373. lastX: pos.lastX,
  374. lastY: pos.lastY,
  375. x: pos.x,
  376. y: pos.y,
  377. button: button,
  378. action: "mouseup"
  379. });
  380. }
  381. }).bind(this);
  382. var handle_mousewheel = (function(e){
  383. if (this.__preventDefaults)
  384. e.preventDefault();
  385. // TODO: Finish me!
  386. }).bind(this);
  387. return (function(enable){
  388. enable = (enable !== false);
  389. if (enable){
  390. window.addEventListener("mousemove", handle_mousemove);
  391. window.addEventListener("mousedown", handle_mousedown);
  392. window.addEventListener("mouseup", handle_mouseup);
  393. window.addEventListener("mousewheel", handle_mousewheel);
  394. } else {
  395. window.removeEventListener("mousemove", handle_mousemove);
  396. window.removeEventListener("mousedown", handle_mousedown);
  397. window.removeEventListener("mouseup", handle_mouseup);
  398. window.removeEventListener("mousewheel", handle_mousewheel);
  399. }
  400. }).bind(this);
  401. }).apply(this);
  402. this.enableKeyboardInput();
  403. this.enableMouseInput();
  404. }
  405. get lastkey(){
  406. if (KEYMAP["lastcode"] !== null){
  407. if (KEYMAP["lastcode"] in Object.keys(KEYBYCODE)){
  408. return KEYBYCODE[KEYMAP["lastcode"]];
  409. }
  410. return "" + KEYMAP["lastcode"];
  411. }
  412. return "0";
  413. }
  414. get lastkeyaction(){
  415. return KEYMAP["lastaction"];
  416. }
  417. get currentKeys(){
  418. return KeymapEventName();
  419. }
  420. get currentKeyCodes(){
  421. return KEYMAP["currentcodes"].map(e=>e[0]);
  422. }
  423. get lastMouseAction(){
  424. return this.__mouseLastAction;
  425. }
  426. get lastMouseButton(){
  427. return this.__mouseLastButton;
  428. }
  429. get lastMousePosition(){
  430. if (this.__mousePosition === null || this.__mousePosition.lastX === null)
  431. return null;
  432. return {
  433. x: this.__mousePosition.lastX,
  434. y: this.__mousePosition.lastY
  435. };
  436. }
  437. get currentMousePosition(){
  438. if (this.__mousePosition === null)
  439. return null;
  440. return {
  441. x: this.__mousePosition.x,
  442. y: this.__mousePosition.y
  443. };
  444. }
  445. get preventDefaults(){return this.__preventDefaults;}
  446. set preventDefaults(p){
  447. this.__preventDefaults = (p === true);
  448. }
  449. get ieMouseMode(){return this.__ieMouseMode;}
  450. set ieMouseMode(m){this.__ieMouseMode = (m === true);}
  451. get mouseTargetElement(){return this.__mouseTarget;}
  452. set mouseTargetElement(el){
  453. if (el === null || Utils.isElement(el)){
  454. this.__mouseTarget = el;
  455. } else {
  456. throw new TypeError("Expected Mouse Target Element to be null or an HTMLElement object.");
  457. }
  458. }
  459. isKeyDown(key){
  460. if (typeof(key) === 'string'){
  461. key = KeyNameToCode(key);
  462. }
  463. for (var i=0; i < KEYMAP["currentcodes"].length; i++){
  464. if (KEYMAP["currentcodes"][i][0] === key){
  465. return true;
  466. }
  467. }
  468. return false;
  469. }
  470. listen(ename, func, owner=null, once=false){
  471. if ((["keyup", "keydown", "keypress", "mousemove", "mousedown", "mouseup"]).indexOf(ename) >= 0){
  472. this.__emitter.listen(ename, func, owner, once);
  473. } else {
  474. ename = ReorderEventName(ename);
  475. if (ename === ""){
  476. throw new ValueError("Failed to parse key or key combination.");
  477. }
  478. this.__emitter.listen(ename, func, owner, once);
  479. }
  480. return this;
  481. }
  482. unlisten(ename, func, owner=null){
  483. if ((["keyup", "keydown", "keypress", "mousemove", "mousedown", "mouseup"]).indexOf(ename) >= 0){
  484. this.__emitter.unlisten(ename, func, owner);
  485. } else {
  486. ename = ReorderEventName(ename);
  487. if (ename !== "")
  488. this.__emitter.unlisten(ename, func, owner);
  489. }
  490. return this;
  491. }
  492. }