|  | @@ -0,0 +1,112 @@
 | 
	
		
			
				|  |  | +const SMALLEST_SIZE = 4.0;
 | 
	
		
			
				|  |  | +const LARGEST_SIZE = 48.0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const DEFAULT_SIZE = 24.0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class State {
 | 
	
		
			
				|  |  | +  updateWindowSize(w, h) {
 | 
	
		
			
				|  |  | +    this.w = w;
 | 
	
		
			
				|  |  | +    this.h = h;
 | 
	
		
			
				|  |  | +    this.canvas.width = w;
 | 
	
		
			
				|  |  | +    this.canvas.height = h;
 | 
	
		
			
				|  |  | +    this.data = {};
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  constructor(canvas, ctx, w, h) {
 | 
	
		
			
				|  |  | +    this.canvas = canvas;
 | 
	
		
			
				|  |  | +    this.ctx = ctx;
 | 
	
		
			
				|  |  | +    this.updateWindowSize(w, h);
 | 
	
		
			
				|  |  | +    this.size = DEFAULT_SIZE;
 | 
	
		
			
				|  |  | +    this.mode = 'square';
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  scroll(delta) {
 | 
	
		
			
				|  |  | +    this.size += delta / 30.0;
 | 
	
		
			
				|  |  | +    if (this.size < SMALLEST_SIZE) {
 | 
	
		
			
				|  |  | +      this.size = SMALLEST_SIZE;
 | 
	
		
			
				|  |  | +    } else if (this.size > LARGEST_SIZE) {
 | 
	
		
			
				|  |  | +      this.size = LARGEST_SIZE;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  draw(mouseLoc) {
 | 
	
		
			
				|  |  | +    this.ctx.clearRect(0, 0, this.w, this.h);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (mouseLoc !== null) {
 | 
	
		
			
				|  |  | +      let [fx, fy] = mouseLoc;
 | 
	
		
			
				|  |  | +      this.ctx.fillStyle = '#f00';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      if (this.mode === 'square') {
 | 
	
		
			
				|  |  | +        fx = Math.floor(fx / this.size);
 | 
	
		
			
				|  |  | +        fy = Math.floor(fy / this.size);
 | 
	
		
			
				|  |  | +        this.ctx.fillRect(fx * this.size, fy * this.size, this.size, this.size);
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        this.ctx.strokeStyle = '#f00';
 | 
	
		
			
				|  |  | +        const half = this.size / 2
 | 
	
		
			
				|  |  | +        fx = Math.floor((fx + half) / this.size);
 | 
	
		
			
				|  |  | +        fy = Math.floor((fy + half) / this.size);
 | 
	
		
			
				|  |  | +        this.ctx.moveTo(fx * this.size, fy * this.size);
 | 
	
		
			
				|  |  | +        this.ctx.arc(fx * this.size, fy * this.size, 5, 0, Math.PI * 2, true);
 | 
	
		
			
				|  |  | +        this.ctx.stroke();
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    this.ctx.strokeStyle = '#000';
 | 
	
		
			
				|  |  | +    for (let x = 0; Math.floor(x / this.size < this.w); x++) {
 | 
	
		
			
				|  |  | +      this.ctx.beginPath();
 | 
	
		
			
				|  |  | +      this.ctx.moveTo(x * this.size, 0);
 | 
	
		
			
				|  |  | +      this.ctx.lineTo(x * this.size, this.h);
 | 
	
		
			
				|  |  | +      this.ctx.stroke();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    for (let y = 0; Math.floor(y / this.size < this.h); y++) {
 | 
	
		
			
				|  |  | +      this.ctx.beginPath();
 | 
	
		
			
				|  |  | +      this.ctx.moveTo(0, y * this.size);
 | 
	
		
			
				|  |  | +      this.ctx.lineTo(this.w, y * this.size);
 | 
	
		
			
				|  |  | +      this.ctx.stroke();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +let main = () => {
 | 
	
		
			
				|  |  | +  const canvas = document.getElementById('cv');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  const w = window.innerWidth;
 | 
	
		
			
				|  |  | +  const h = window.innerHeight;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  const ctx = canvas.getContext('2d');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  let state = new State(canvas, ctx, w, h);
 | 
	
		
			
				|  |  | +  let last_mouse = null;
 | 
	
		
			
				|  |  | +  state.draw(last_mouse);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  document.addEventListener('mousemove', e => {
 | 
	
		
			
				|  |  | +    last_mouse = [e.clientX, e.clientY];
 | 
	
		
			
				|  |  | +    state.draw(last_mouse);
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  document.addEventListener('wheel', e => {
 | 
	
		
			
				|  |  | +    state.scroll(-e.deltaY);
 | 
	
		
			
				|  |  | +    state.draw(last_mouse);
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  window.addEventListener('resize', () => {
 | 
	
		
			
				|  |  | +    state.updateWindowSize(
 | 
	
		
			
				|  |  | +      window.innerWidth,
 | 
	
		
			
				|  |  | +      window.innerHeight,
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +    state.draw(last_mouse);
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  document.addEventListener('keydown', e => {
 | 
	
		
			
				|  |  | +    if (e.key === ' ') {
 | 
	
		
			
				|  |  | +      if (state.mode === 'square')
 | 
	
		
			
				|  |  | +        state.mode = 'dot';
 | 
	
		
			
				|  |  | +      else
 | 
	
		
			
				|  |  | +        state.mode = 'square';
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    state.draw(last_mouse);
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +window.onload = main;
 |