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文字以内のものにしてください。

321 行
7.7KB

  1. import GlobalEvents from "/app/js/common/EventCaller.js";
  2. import Utils from "/app/js/common/Utils.js";
  3. import JSONSchema from "/app/js/common/JSONSchema.js";
  4. import EditableText from "/app/js/ui/EditableText.js";
  5. import NESPalette from "/app/js/models/NESPalette.js";
  6. const PLI_TEMPLATE = "palette-list-item-template";
  7. const PLI_TITLE = "title";
  8. const PLI_SELECTED = "list-item-selected";
  9. const PLI_BG_COLOR = "pal-bg-color";
  10. const PLI_FG_BASE = "pal-fg-";
  11. const PLI_BG_BASE = "pal-bg-";
  12. const PLI_COLOR_BASE = "nes-color-bg-";
  13. var Palettes = [];
  14. var CurrentPaletteIndex = 0;
  15. var BlockEmits = false;
  16. const SCHEMA_ID="http://nespaint/PalettesStoreSchema.json";
  17. JSONSchema.add({
  18. "$schema": "http://json-schema.org/draft-07/schema#",
  19. "$id": SCHEMA_ID,
  20. "type": "array",
  21. "items": {
  22. "type": "object",
  23. "properties": {
  24. "name":{
  25. "type":"string",
  26. "minLength":1
  27. },
  28. "palette":{
  29. "$ref":"http://nespaint/NESPaletteSchema.json"
  30. }
  31. },
  32. "required":["name","palette"]
  33. }
  34. });
  35. function HANDLE_PaletteClick(e){
  36. if (!this.hasAttribute("palname")){return;}
  37. var pname = this.getAttribute("palname");
  38. if (Palettes.length > 0){
  39. if (Palettes[CurrentPaletteIndex][0].value !== pname || !Palettes[CurrentPaletteIndex][2].classList.contains(PLI_SELECTED)){
  40. var oel = Palettes[CurrentPaletteIndex][2];
  41. oel.classList.remove(PLI_SELECTED);
  42. for (let i=0; i < Palettes.length; i++){
  43. if (Palettes[i][0].value === pname){
  44. Palettes[i][2].classList.add(PLI_SELECTED);
  45. CurrentPaletteIndex = i;
  46. GlobalEvents.emit("set_app_palette", Palettes[i][1]);
  47. break;
  48. }
  49. }
  50. }
  51. }
  52. }
  53. function SetElPaletteName(el, pname){
  54. el.setAttribute("palname", pname);
  55. var et = new EditableText(el, "title");
  56. et.value = pname;
  57. et.listen("value_change", (v) => {el.setAttribute("palname", v);});
  58. return et;
  59. /*var sel = el.querySelectorAll("." + PLI_TITLE);
  60. if (sel.length === 1){
  61. sel = sel[0];
  62. sel.innerHTML = pname;
  63. }*/
  64. }
  65. function SetElToColor(el, mode, pi, ci, hex){
  66. var cel = null;
  67. if (ci === 0){
  68. cel = el.querySelectorAll("." + PLI_BG_COLOR);
  69. } else {
  70. cel = el.querySelectorAll("." + ((mode == 0) ? PLI_FG_BASE : PLI_BG_BASE) + pi + "-" + ci);
  71. }
  72. if (cel !== null && cel.length === 1){
  73. cel = cel[0];
  74. var clist = cel.getAttribute("class").split(" ");
  75. for (let i=0; i < clist.length; i++){
  76. if (clist[i].startsWith(PLI_COLOR_BASE)){
  77. cel.classList.remove(clist[i]);
  78. break;
  79. }
  80. }
  81. cel.classList.add(PLI_COLOR_BASE + hex.toUpperCase());
  82. }
  83. }
  84. function ColorElementToPalette(el, palette){
  85. SetElToColor(
  86. el, 0,
  87. 0, 0,
  88. palette.get_palette_syscolor_index(0,0,true)
  89. );
  90. for (let p=0; p < 8; p++){
  91. for (let c=1; c < 4; c++){
  92. SetElToColor(
  93. el, (p >= 4) ? 0 : 1,
  94. p%4, c,
  95. palette.get_palette_syscolor_index(p,c,true)
  96. );
  97. }
  98. }
  99. }
  100. function ConnectElementToPalette(el, palette){
  101. palette.listen("palettes_changed", (e) => {
  102. if (e.type == "ALL"){
  103. if (e.hasOwnProperty("cindex")){
  104. SetElToColor(
  105. el, 0,
  106. 0, 0,
  107. palette.get_palette_syscolor_index(0,0,true)
  108. );
  109. } else {
  110. ColorElementToPalette(el, palette);
  111. }
  112. } else if (e.type == "SPRITE"){
  113. SetElToColor(
  114. el, 0,
  115. e.pindex%4, e.cindex,
  116. palette.get_palette_syscolor_index(e.pindex, e.cindex, true)
  117. );
  118. } else if (e.type == "TILE"){
  119. SetElToColor(
  120. el, 1,
  121. e.pindex, e.cindex,
  122. palette.get_palette_syscolor_index(e.pindex, e.cindex, true)
  123. );
  124. }
  125. });
  126. }
  127. function CreatePaletteDOMEntry(pname, palette){
  128. var oel = document.querySelectorAll("." + PLI_TEMPLATE);
  129. if (oel.length == 1){
  130. var el = oel[0].cloneNode(true);
  131. el.classList.remove(PLI_TEMPLATE);
  132. el.classList.remove("hidden");
  133. ConnectElementToPalette(el, palette);
  134. ColorElementToPalette(el, palette);
  135. //SetElPaletteName(el, pname);
  136. el.addEventListener("click", HANDLE_PaletteClick);
  137. oel[0].parentNode.appendChild(el);
  138. return el;
  139. } else {
  140. console.log("WARNING: Multiple templates found. Ambigous state.");
  141. }
  142. return null;
  143. }
  144. class CTRLPalettesStore{
  145. constructor(){
  146. GlobalEvents.listen("palstore-add", (function(e){
  147. if (e.hasOwnProperty("palname")){
  148. this.createPalette(e.palname);
  149. this.activatePalette(e.palname);
  150. }
  151. }).bind(this));
  152. GlobalEvents.listen("palstore-remove", (function(e){
  153. this.removePalette(Palettes[CurrentPaletteIndex][0]);
  154. }).bind(this));
  155. }
  156. get obj(){
  157. var d = [];
  158. for (let i=0; i < Palettes.length; i++){
  159. d.push({name:Palettes[i][0].value, palette:Palettes[i][1].obj});
  160. }
  161. return d;
  162. }
  163. set obj(d){
  164. var validator = JSONSchema.getValidator(SCHEMA_ID);
  165. if (validator !== null && validator(d)){
  166. this.clear();
  167. for (let i=0; i < d.length; i++){
  168. this.createPalette(d[i].name, JSON.stringify(d[i].palette));
  169. }
  170. } else {
  171. throw new Error("Object failed to validate against PalettesStoreSchema.");
  172. }
  173. }
  174. get json(){
  175. return JSON.stringify(this.obj);
  176. }
  177. set json(j){
  178. try {
  179. this.obj = JSON.parse(j);
  180. } catch (e) {
  181. throw e;
  182. }
  183. }
  184. initialize(){
  185. if (Palettes.length <= 0)
  186. this.createPalette("Palette");
  187. return this;
  188. }
  189. paletteIndexFromName(name){
  190. for (let i=1; i < Palettes.length; i++){
  191. if (Palettes[i][0].value == name){
  192. return i;
  193. }
  194. }
  195. return -1;
  196. }
  197. getPalette(name){
  198. var i = this.paletteIndexFromName(name);
  199. return (i >= 0) ? Palettes[i][1] : null;
  200. }
  201. createPalette(name, pjson){
  202. var palette = this.getPalette(name);
  203. if (palette === null){
  204. palette = new NESPalette();
  205. if (typeof(pjson) === "string"){
  206. try {
  207. palette.json = pjson;
  208. } catch (e) {
  209. console.log("Failed to create palette.", e.toString());
  210. palette = null;
  211. }
  212. } else {
  213. palette.set_palette([
  214. "0F",
  215. "05","06","07",
  216. "09","0A","0B",
  217. "01","02","03",
  218. "0D","00","20",
  219. "15","16","17",
  220. "19","1A","1B",
  221. "11","21","31",
  222. "1D","10","30"
  223. ]);
  224. }
  225. if (palette !== null){
  226. var el = CreatePaletteDOMEntry(name, palette);
  227. var eltitle = SetElPaletteName(el, name);
  228. Palettes.push([eltitle, palette, el]);
  229. if (Palettes.length <= 1 && !BlockEmits){
  230. el.click();
  231. }
  232. }
  233. }
  234. return this;
  235. }
  236. removePalette(name){
  237. for (let i=0; i < Palettes.length; i++){
  238. if (Palettes[i][0].value === name){
  239. if (CurrentPaletteIndex === i){
  240. CurrentPaletteIndex = 0;
  241. this.activatePalette(Palettes[0][0].value);
  242. }
  243. Palettes[i][2].parentNode.removeChild(Palettes[i][2]);
  244. Palettes.splice(i, 1);
  245. }
  246. }
  247. if (Palettes.length <= 0){
  248. this.createPalette("Palette");
  249. } else {
  250. Palettes[CurrentPaletteIndex][2].click();
  251. }
  252. return this;
  253. }
  254. renamePalette(oldname, newname){
  255. var i = this.paletteIndexFromName(oldname);
  256. if (i < 0)
  257. throw new ValueError("Failed to find palette named '" + oldname +"'. Cannot rename.");
  258. Palettes[i][0].value = newname;
  259. //SetElPaletteName(Palettes[i][2], newname);
  260. return this;
  261. }
  262. activatePalette(name){
  263. var i = this.paletteIndexFromName(name);
  264. if (i >= 0 && CurrentPaletteIndex !== i){
  265. Palettes[CurrentPaletteIndex][2].click();
  266. }
  267. return this;
  268. }
  269. clear(){
  270. for (let i=0; i < Palettes.length; i++){
  271. Palettes[i][2].parentNode.removeChild(Palettes[i][2]);
  272. }
  273. Palettes = [];
  274. CurrentPaletteIndex = 0;
  275. }
  276. }
  277. const instance = new CTRLPalettesStore();
  278. export default instance;