// ************ CUSTOMIZABLE CONFIGURATION ************ // // TODO: These should just be text boxes/color pickers var Config = { minColCount: 16, minRowCount: 48, zoomRate: 4, gridScale: 24, gridWidth: 0.4, gridColor:'rgba(0,0,0,0.5)', emptyColor: 'rgb(255,255,255)' }; // ******************* ACTUAL CODE ******************* // //-----------------------------------------------------// // Initial Setup // //-----------------------------------------------------// var Page = {body:0, cvs:0, ctx:0, footer:0, btnZoomIn:0, btnZoomOut:0, l:0, t:0, w:0, h:0, u:0, minU:12, maxU:48, colCount: 0, rowCount: 0, isDirty:0}; var Tool = {active:0, draw:0, move:0, delete:0}; var ObjActive = 0, ObjStatic = []; Load(); function Load(){ Page.body = document.getElementsByTagName('body')[0]; Page.cvs = document.createElement('canvas'); Page.ctx = Page.cvs.getContext('2d'); Page.body.appendChild(Page.cvs); Page.cvs.style.position = 'absolute'; Page.footer = document.getElementById('footer'); Page.btnZoomIn = document.getElementById('zoomIn'); Page.btnZoomOut = document.getElementById('zoomOut'); Tool.draw = document.getElementById('draw'); Tool.move = document.getElementById('move'); Tool.delete = document.getElementById('delete'); Tool.active = Tool.draw; Tool.state = 0; Page.u = Config.gridScale; //SetInputEvents(); Init(); } function Init(){ ObjActive = 0; ObjStatic = []; Page.isDirty = 1; CheckWindowSize(); UpdateLoop(); } function CheckWindowSize(){ var w = Page.body.offsetWidth, h = Page.body.offsetHeight - Page.footer.offsetHeight; if (w !== Page.w || h !== Page.h){ Page.cvs.width = w; Page.cvs.height = h; SetScale(w,h,Page.u); } } function SetScale(w,h,u){ Page.isDirty = 1; Page.w = (w !== null) ? w : Page.w; Page.h = (h !== null) ? h : Page.h; Page.u = (u !== null) ? u : Page.u; Page.colCount = Math.ceil(Page.w / Page.u); Page.rowCount = Math.ceil(Page.h / Page.u); Page.w; Page.cvs.height = Page.h; Page.cvs.style.left = Page.cvs.style.top = 0; } function TryZoom(dir){ if (CanZoom(dir)){ var newU = Clamp(Page.u+dir*Config.zoomRate,Page.minU,Page.maxU); if (newU === Page.minU){ AddClass(Page.btnZoomOut,'btn-inactive'); } else if (Page.u === Page.minU){ RemoveClass(Page.btnZoomOut,'btn-inactive'); } if (newU === Page.maxU){ AddClass(Page.btnZoomIn,'btn-inactive'); } else if (Page.u === Page.maxU){ RemoveClass(Page.btnZoomIn,'btn-inactive'); } SetScale(Page.w,Page.h,newU); } } function CanZoom(dir){ return ((Page.u>Page.minU&&dir<0) || (Page.u0)); } function TrySelectTool(thisTool){ if (thisTool != Tool.active){ RemoveClass(Tool.active,'btn-select'); AddClass(thisTool,'btn-select'); Tool.active = thisTool; Tool.state = GetToolState(); } } function GetToolState(){ switch (Tool.active){ case Tool.draw: return 0; case Tool.move: return 1; case Tool.delete: return 2; default: return -1; } } function DrawBackground(){ Page.ctx.fillStyle = Config.emptyColor; Page.ctx.fillRect(0, 0, Page.w, Page.h); } function DrawGrid(){ var i, len; Page.ctx.beginPath(); for(i = 1, len = Page.colCount ; i < len ; i++){ Page.ctx.moveTo(i * Page.u, 0); Page.ctx.lineTo(i * Page.u, Page.h); } for(i = 1, len = Page.rowCount ; i < len ; i++){ Page.ctx.moveTo(0, i * Page.u); Page.ctx.lineTo(Page.w, i * Page.u); } Page.ctx.lineWidth = Config.gridWidth; Page.ctx.strokeStyle = Config.gridColor; Page.ctx.stroke(); } //-----------------------------------------------------// // User Input Events // //-----------------------------------------------------// window.addEventListener('resize', CheckWindowSize, false); /* function SetInputEvents(){ Page.isMobile = IsMobile(); // TOUCH EVENTS if (Page.isMobile){ document.getElementById('reset').addEventListener("touchstart", Init(), false); Page.btnZoomOut.addEventListener("touchstart", TryZoom(-1), false); Page.btnZoomIn.addEventListener("touchstart", TryZoom(1), false); Tool.draw.addEventListener("touchstart", TrySelectTool(Tool.draw), false); Tool.move.addEventListener("touchstart", TrySelectTool(Tool.move), false); Tool.delete.addEventListener("touchstart", TrySelectTool(Tool.delete), false); Page.cvs.addEventListener("touchstart", CheckClick(evt), false); Page.cvs.addEventListener("touchend", CheckUnclick(), false); Page.cvs.addEventListener("touchleave", CheckUnclick(), false); Page.cvs.addEventListener("touchmove", CheckMouseMove(evt), false); //Page.cvs.addEventListener("touchcancel", handleCancel, false); } // MOUSE EVENTS else{*/ document.getElementById('reset').onclick = function(){ Init(); }; Page.btnZoomOut.onclick = function(){ TryZoom(-1); }; Page.btnZoomIn.onclick = function(){ TryZoom(1); }; Tool.draw.onclick = function(){TrySelectTool(Tool.draw)}; Tool.move.onclick = function(){TrySelectTool(Tool.move)}; Tool.delete.onclick = function(){TrySelectTool(Tool.delete)}; Page.cvs.onmousedown = function(evt){ CheckClick(evt); document.onmousemove = function(evt){ CheckMouseMove(evt); }; document.onmouseup = function(evt){ CheckUnclick(evt); }; }; // INPUT FUNCTIONS function CheckUnclick() { if (Tool.state === 0){ if (ObjActive !== 0 && ObjActive !== 1){ if (ObjActive.curX != ObjActive.anchorX && ObjActive.curY != ObjActive.anchorY){ ObjStatic.push(ObjActive); } ObjActive = 1; } } else if (Tool.state === 2){ } } function CheckClick(evt) { if (Tool.state === 0){ var unitPos = GetMousePosUnits(evt); // TODO: null coords is pretty hacky, should return 3rd param if (unitPos.x !== null && unitPos.y !== null){ isLooping = (ObjActive === 0 || ObjActive === 1) ? true : false; ObjActive = new DrawObj(unitPos.x,unitPos.y,'rgba(96,160,192,1)'); Page.isDirty = 1; } } else if (Tool.state === 2){ TryDeleteDrawArea(evt); } } function CheckMouseMove(evt){ if(Tool.state === 0){ if (ObjActive !== 0 && ObjActive !== 1){ ObjActive.UpdateCoords(evt); } } } function GetMousePosUnits(evt) { var pixelPos = GetMousePosPixels(evt); return PixelsToUnits(pixelPos.x,pixelPos.y); } function GetMousePosPixels(evt) { var xPos = evt.clientX - Page.l, yPos = evt.clientY - Page.t; xPos = Math.min(Math.max(xPos,0),Page.w); yPos = Math.min(Math.max(yPos,0),Page.h); return {x: xPos, y: yPos}; } function PixelsToUnits(x, y){ var unitX = null, unitY = null; if (x > -10 && x <= Page.w+10 && y >= -10 && y <= Page.h+10) { unitX = Math.round(x/Page.u); unitY = Math.round(y/Page.u); } return { x: unitX, y: unitY }; } function TryDeleteDrawArea(evt){ var point = GetMousePosPixels(evt); // Search backwards from the newest for(var i = ObjStatic.length - 1; i >= 0; i--){ if (PointInRect(point,ObjStatic[i].ToPixelRect())){ ObjStatic.splice(i,1); Page.isDirty = 1; break; } } } var DrawObj = function(anchorX, anchorY, fillStyle){ this.fillStyle = fillStyle; this.anchorX = anchorX; this.anchorY = anchorY; this.curX = anchorX; this.curY = anchorY; this.UpdateCoords = function(evt){ var unitPos = GetMousePosUnits(evt); if (unitPos.x != this.curX || unitPos.y != this.curY){ this.curX = Math.min(Math.max(unitPos.x,0),Page.colCount); this.curY = Math.min(Math.max(unitPos.y,0),Page.rowCount); } }; this.ToPixelRect = function(){ var l = Page.u * Math.min(this.anchorX, this.curX), r = Page.u * Math.max(this.anchorX, this.curX), t = Page.u * Math.min(this.anchorY, this.curY), b = Page.u * Math.max(this.anchorY, this.curY); return {left:l, top:t, right:r, bottom:b} }; this.Draw = function(isActive){ var left = this.anchorX * Page.u, top = this.anchorY * Page.u, right = this.curX * Page.u, bottom = this.curY * Page.u, width = right - left, height = bottom - top; Page.ctx.fillStyle = this.fillStyle; Page.ctx.fillRect(left, top, width, height); // flashing effect for the active draw area if (isActive === 1){ var alpha = 0.2 + Math.sin(Date.now()/80)/20; Page.ctx.fillStyle = 'rgba(255,255,255,' + alpha + ')'; Page.ctx.fillRect(left, top, width, height); Page.ctx.beginPath(); Page.ctx.moveTo(left,top); } Page.ctx.lineWidth = 1.5; Page.ctx.strokeStyle = 'rgba(0,0,0,0.5)'; Page.ctx.strokeRect(left, top, width, height); // visuals on the anchor point and current point if (isActive === 1){ Page.ctx.beginPath(); Page.ctx.arc(left,top,Page.u/5,0,2*Math.PI,false); Page.ctx.arc(right,bottom,Page.u/5,0,2*Math.PI,false); Page.ctx.fillStyle = this.fillStyle; Page.ctx.fill(); Page.ctx.fillStyle = 'rgba(0,0,0,0.5)'; Page.ctx.fill(); } }; }; //-----------------------------------------------------// // Draw Update Loop // //-----------------------------------------------------// function UpdateLoop(){ if (Page.isDirty === 1){ DrawBackground(); for(var i = 0, len = ObjStatic.length; i < len; i++){ ObjStatic[i].Draw(0); } // TODO: stop being so hacky with the ObjActive 0 and 1 stuff if (ObjActive !== 0 && ObjActive !== 1){ ObjActive.Draw(1); } else{ Page.isDirty = ObjActive = 0; } DrawGrid(); } window.requestAnimationFrame(UpdateLoop); } //-----------------------------------------------------// // Helper Functions // //-----------------------------------------------------// /* function IsMobile() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera); return check; }*/ function PointInRect(p, r){ return (p.x>=r.left&&p.x<=r.right&&p.y>=r.top&&p.y<=r.bottom); } function Clamp(n,min,max){ return Math.min(Math.max(n,min),max); } function AddClass(ele,classStr){ ele.className = ele.className.replaceAll(' '+classStr,'')+' '+classStr; } function RemoveClass(ele,classStr){ ele.className = ele.className.replaceAll(' '+classStr,''); } function ToggleClass(ele,classStr){ var str = ele.className.replaceAll(' '+classStr,''); ele.className = (str.length===ele.className.length)?str+' '+classStr:str; } String.prototype.replaceAll = function (replaceThis, withThis) { var re = new RegExp(replaceThis,"g"); return this.replace(re, withThis); }; function onTouch(evt) { evt.preventDefault(); if (evt.touches.length > 1 || (evt.type == "touchend" && evt.touches.length > 0)) return; var newEvt = document.createEvent("MouseEvents"); var type = null; var touch = null; switch (evt.type) { case "touchstart": type = "mousedown"; touch = evt.changedTouches[0];break; case "touchmove": type = "mousemove"; touch = evt.changedTouches[0];break; case "touchend": type = "mouseup"; touch = evt.changedTouches[0];break; } newEvt.initMouseEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, evt.ctrlKey, evt.altKey, evt.shirtKey, evt.metaKey, 0, null); evt.originalTarget.dispatchEvent(newEvt); }