A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.

353 lines
8.0KB

  1. import {EventCaller} from "/app/js/common/EventCaller.js";
  2. // Keycode list based on...
  3. // https://keycode.info/
  4. var KEYBYCODE = {
  5. 3:"break",
  6. 8:"backspace",
  7. 9:"tab",
  8. 13:"enter",
  9. 16:"shift",
  10. 17:"ctrl",
  11. 18:"alt",
  12. 19:"pause",
  13. 20:"capslock",
  14. 27:"esc",
  15. 32:"space",
  16. 33:"pageup",
  17. 34:"pagedown",
  18. 35:"end",
  19. 36:"home",
  20. 37:"left",
  21. 38:"up",
  22. 39:"right",
  23. 40:"down",
  24. 41:"select",
  25. 42:"print",
  26. 43:"execute",
  27. 44:"printscreen",
  28. 45:"insert",
  29. 46:"delete",
  30. 47:"help",
  31. 48:"0",
  32. 49:"1",
  33. 50:"2",
  34. 51:"3",
  35. 52:"4",
  36. 53:"5",
  37. 54:"6",
  38. 55:"7",
  39. 56:"8",
  40. 57:"9",
  41. 65:"a",
  42. 66:"b",
  43. 67:"c",
  44. 68:"d",
  45. 69:"e",
  46. 70:"f",
  47. 71:"g",
  48. 72:"h",
  49. 73:"i",
  50. 74:"j",
  51. 75:"k",
  52. 76:"l",
  53. 77:"m",
  54. 78:"n",
  55. 79:"o",
  56. 80:"p",
  57. 81:"q",
  58. 82:"r",
  59. 83:"s",
  60. 84:"t",
  61. 85:"u",
  62. 86:"v",
  63. 87:"w",
  64. 88:"x",
  65. 89:"y",
  66. 90:"z",
  67. 91:"leftmod", // Window key (left)
  68. 92:"rightwin",// Window key (right)
  69. 93:"rightmod",// Window key (right)
  70. 96:"num0",
  71. 97:"num1",
  72. 98:"num2",
  73. 99:"num3",
  74. 100:"num4",
  75. 101:"num5",
  76. 102:"num6",
  77. 103:"num7",
  78. 104:"num8",
  79. 105:"num9",
  80. 112:"f1",
  81. 113:"f2",
  82. 114:"f3",
  83. 115:"f4",
  84. 116:"f5",
  85. 117:"f6",
  86. 118:"f7",
  87. 119:"f8",
  88. 120:"f9",
  89. 121:"f10",
  90. 122:"f11",
  91. 123:"f12",
  92. 144:"numlock",
  93. 145:"scrolllock",
  94. };
  95. var KEYBYNAME = (function(){
  96. var keys = Object.keys(KEYBYCODE);
  97. var o = {};
  98. for (var i=0; i < keys.length; i++){
  99. if (KEYBYCODE.hasOwnProperty(keys[i])){
  100. o[KEYBYCODE[keys[i]]] = keys[i];
  101. }
  102. }
  103. return o;
  104. })();
  105. var KEYTYPE = {
  106. "number":[48,49,50,51,52,53,54,55,56,57,96,97,98,99,100,101,102,103,104,105],
  107. "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],
  108. "mod":[16,17,18],
  109. "arrow":[37,38,39,40],
  110. "wasd":[87,65,83,68],
  111. "fn":[112,113,114,115,116,117,118,119,120,121,122,123],
  112. "n1":[48,96],
  113. "n2":[49,97],
  114. "n3":[50,98],
  115. "n4":[51,99],
  116. "n5":[52,100],
  117. "n6":[53,101],
  118. "n7":[54,102],
  119. "n8":[55,103],
  120. "n9":[56,104],
  121. "n0":[57,105]
  122. };
  123. var KEYMAP = {
  124. "lastcode":null,
  125. "lastaction":"",
  126. "currentcodes":[]
  127. };
  128. // TODO: Reeval this idea.
  129. const KEYPRESS_DELAY = 350; // Time in milliseconds. NOTE: May make this a variable in future.
  130. function AssignCodeName(code, name){
  131. name = name.toLowerCase();
  132. var remove = (code in Object.keys(KEYBYCODE));
  133. if (name in Object.keys(KEYBYNAME)){
  134. if (remove && KEYBYCODE[code] === name){
  135. return; // We're being asked to replace the exact same thing. SKIP!
  136. }
  137. throw new ValueError("Key name '" + name + "' already assigned. Cannot use duplicate key names.");
  138. }
  139. if (remove){
  140. delete KEYBYNAME[KEYBYCODE[code]];
  141. }
  142. KEYBYCODE[code] = name;
  143. KEYBYNAME[name] = code;
  144. }
  145. function KeymapContains(code){
  146. return KEYMAP["currentcodes"].findIndex(c=>c[0] == code) >= 0;
  147. }
  148. function AddToKeymap(code, action){
  149. KEYMAP["lastcode"] = code;
  150. KEYMAP["lastaction"] = action;
  151. if (KeymapContains(code) == false){
  152. KEYMAP["currentcodes"].push([code, Math.floor(Date.now())]);
  153. if (KEYMAP["currentcodes"].length > 1){
  154. KEYMAP["currentcodes"].sort(function(a, b){return a[0] - b[0];});
  155. }
  156. return true;
  157. }
  158. return false;
  159. }
  160. function RemoveFromKeymap(code, action){
  161. KEYMAP["lastcode"] = code;
  162. KEYMAP["lastaction"] = action;
  163. var ctime = Math.floor(Date.now());
  164. for (var i=0; i < KEYMAP["currentcodes"].length; i++){
  165. if (KEYMAP["currentcodes"][i][0] === code){
  166. var timediff = ctime - KEYMAP["currentcodes"][i][1];
  167. KEYMAP["currentcodes"].splice(i, 1);
  168. return timediff;
  169. }
  170. }
  171. return -1;
  172. }
  173. function KeyNameToCode(key){
  174. return (key in Object.keys(KEYBYNAME)) ? KEYBYNAME[key] : -1;
  175. }
  176. function CodeToKeyName(code){
  177. return (code in Object.keys(KEYBYCODE)) ? KEYBYCODE[code] : "" + code;
  178. }
  179. function CodesToEventName(codes){
  180. var ename = "";
  181. for (var i=0; i < codes.length; i++){
  182. ename += ((ename !== "") ? "+" : "") + CodeToKeyName(codes[i]);
  183. }
  184. return ename;
  185. }
  186. function KeymapEventName(){
  187. return CodesToEventName(KEYMAP["currentcodes"].map(e=>e[0]));
  188. }
  189. function ReorderEventName(ename){
  190. // This function takes a keyboard event name and reorders it into key-code order.
  191. // This way users can write the event any way they want, but should still result in proper
  192. // event being called.
  193. var elist = ename.split("+");
  194. var ecodes = [];
  195. for (var i=0; i < elist.length; i++){
  196. var key = elist[i].trim().toLowerCase();
  197. if (!(key in Object.keys(KEYBYNAME))){
  198. if (!Number.isNaN(key))
  199. ecodes.push(parseInt(key));
  200. else
  201. return ""; // This event name does not include valid key name!
  202. } else {
  203. ecodes.push(KEYBYNAME[key]);
  204. }
  205. }
  206. if (ecodes.length > 0){
  207. ecodes.sort(function(a, b){return a-b;});
  208. return CodesToEventName(ecodes);
  209. }
  210. return "";
  211. }
  212. export default class Input{
  213. constructor(){
  214. this.__emitter = new EventCaller();
  215. this.enableKeyboardInput = (function(){
  216. var handle_keydown = (function(e){
  217. if (AddToKeymap(e.keyCode, "keydown")){
  218. var ename = KeymapEventName();
  219. var edata = {
  220. source: this,
  221. iscombo: (ename.indexOf("+") >= 0),
  222. keys: ename,
  223. keycode:e.keyCode,
  224. keyname:CodeToKeyName(e.keyCode),
  225. action:"keydown"
  226. }
  227. this.__emitter.emit(ename, edata);
  228. this.__emitter.emit("keydown", edata);
  229. }
  230. }).bind(this);
  231. var handle_keyup = (function(e){
  232. var timediff = RemoveFromKeymap(e.keyCode, "keyup");
  233. if (timediff < 0){
  234. console.log("WARNING: Failed to find keycode '" + e.keyCode + "' in the Key Map.");
  235. } else {
  236. var ename = KeymapEventName();
  237. var edata = {
  238. source: this,
  239. iscombo: (ename.indexOf("+") >= 0),
  240. keys: ename,
  241. keycode: e.keyCode,
  242. keyname: CodeToKeyName(e.keyCode),
  243. action:"keyup"
  244. }
  245. if (timediff <= KEYPRESS_DELAY && KEYMAP["currentcodes"].length <= 0){
  246. this.__emitter.emit("keypress", edata);
  247. }
  248. this.__emitter.emit("keyup", edata);
  249. }
  250. }).bind(this);
  251. return (function(enable){
  252. enable = (enable !== false);
  253. if (enable){
  254. window.addEventListener("keydown", handle_keydown, false);
  255. //window.addEventListener("keypress", handle_keypress, false);
  256. window.addEventListener("keyup", handle_keyup, false);
  257. } else {
  258. window.removeEventListener("keydown", handle_keydown);
  259. window.removeEventListener("keyup", handle_keyup);
  260. }
  261. }).bind(this);
  262. }).apply(this);
  263. this.enableKeyboardInput();
  264. }
  265. get lastkey(){
  266. if (KEYMAP["lastcode"] !== null){
  267. if (KEYMAP["lastcode"] in Object.keys(KEYBYCODE)){
  268. return KEYBYCODE[KEYMAP["lastcode"]];
  269. }
  270. return "" + KEYMAP["lastcode"];
  271. }
  272. return "0";
  273. }
  274. get lastaction(){
  275. return KEYMAP["lastaction"];
  276. }
  277. get currentKeys(){
  278. return KeymapEventName();
  279. }
  280. get currentCodes(){
  281. return KEYMAP["currentcodes"].map(e=>e[0]);
  282. }
  283. isKeyDown(key){
  284. if (typeof(key) === 'string'){
  285. key = KeyNameToCode(key);
  286. }
  287. for (var i=0; i < KEYMAP["currentcodes"].length; i++){
  288. if (KEYMAP["currentcodes"][i][0] === key){
  289. return true;
  290. }
  291. }
  292. return false;
  293. }
  294. listen(ename, func, owner=null, once=false){
  295. if ((["keyup", "keydown", "keypress", "mousemove", "mousedown", "mouseup"]).indexOf(ename) >= 0){
  296. this.__emitter.listen(ename, func, owner, once);
  297. } else {
  298. ename = ReorderEventName(ename);
  299. if (ename === ""){
  300. throw new ValueError("Failed to parse key or key combination.");
  301. }
  302. this.__emitter.listen(ename, func, owner, once);
  303. }
  304. return this;
  305. }
  306. unlisten(ename, func, owner=null){
  307. if ((["keyup", "keydown", "keypress", "mousemove", "mousedown", "mouseup"]).indexOf(ename) >= 0){
  308. this.__emitter.unlisten(ename, func, owner);
  309. } else {
  310. ename = ReorderEventName(ename);
  311. if (ename !== "")
  312. this.__emitter.unlisten(ename, func, owner);
  313. }
  314. return this;
  315. }
  316. }