A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
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. }