diff --git a/core/core.js b/core/core.js index 3b6b8bd..127903b 100644 --- a/core/core.js +++ b/core/core.js @@ -10,6 +10,36 @@ */ let STATE = _SETTINGS.setup.initialState; +const { pixelCrud, lineCrud, rectCrud, polyCrud, stateCrud, maskCrud } = { + pixelCrud: new Crud(CrudStore.Pixels), + lineCrud: new Crud(CrudStore.Lines), + rectCrud: new Crud(CrudStore.Rectangles), + polyCrud: new Crud(CrudStore.Polygons), + stateCrud: new Crud(CrudStore.State), + maskCrud: new Crud(CrudStore.Mask), +}; +// Usage sample of new CRUD implementation +/* +lineCrud.create({point1: [0,0]}); +lineCrud.create({point1: [55,2]}); +lineCrud.create({point1: [1,4]}); +console.log(lineCrud.getAll()); +*/ + +const PolyMan = new PolygonContainer(); + + +const countSizes = () => { + _SETTINGS.general.activeArea.top = 0; + _SETTINGS.general.activeArea.left = Math.floor((_SETTINGS.toolbar.width + _SETTINGS.toolbar.offset.left + _SETTINGS.toolbar.offset.right) / PIXEL_SIZE) * PIXEL_SIZE; + _SETTINGS.general.activeArea.width = Math.floor((CANVAS_W - (_SETTINGS.toolbar.offset.left + _SETTINGS.toolbar.width + _SETTINGS.toolbar.offset.right)) / PIXEL_SIZE) * PIXEL_SIZE; + _SETTINGS.general.activeArea.height = (Math.floor((CANVAS_H - _SETTINGS.general.activeArea.top) / PIXEL_SIZE) - 1) * PIXEL_SIZE; + _SETTINGS.general.activeArea.cellBorders.left = -Math.floor((CANVAS_W / 2 - _SETTINGS.general.activeArea.left) / PIXEL_SIZE); + _SETTINGS.general.activeArea.cellBorders.top = Math.floor((CANVAS_H / 2 - _SETTINGS.general.activeArea.top + PIXEL_SIZE / 2) / PIXEL_SIZE) - 1; + _SETTINGS.general.activeArea.cellBorders.right = Math.floor((_SETTINGS.general.activeArea.left + _SETTINGS.general.activeArea.width - CANVAS_W / 2) / PIXEL_SIZE); + _SETTINGS.general.activeArea.cellBorders.bottom = -Math.floor((_SETTINGS.general.activeArea.top + _SETTINGS.general.activeArea.height - CANVAS_H / 2 + PIXEL_SIZE / 2) / PIXEL_SIZE); + +}; const getRegion = () => { if (testXY(_SETTINGS.general.activeArea.left, @@ -43,15 +73,17 @@ const handleHotkey = (key) => { }; const drawLineRaw = (point1, point2, color = COLORS.BLACK, thickness = 1) => { - stroke(`rgba(${color.join(',')},0.9)`); - strokeWeight(thickness); + STRCOLORS.STROKE && stroke(color); + STRCOLORS.STROKE ? strokeWeight(thickness) : strokeWeight(0); line(point1[0], point1[1], point2[0], point2[1]); }; const drawLine = (point1, point2, color = STATE.currentColor, thickness = 1, type = [AlgoType.BRZ]) => { - stroke(color); - strokeWeight(thickness); - line(CENTER_W + (point1[0] + 0.5) * PIXEL_SIZE, CENTER_H - (point1[1] + 0.5) * PIXEL_SIZE, CENTER_W + point2[0] * PIXEL_SIZE, CENTER_H - (point2[1] + 0.5) * PIXEL_SIZE); + if (type.includes(AlgoType.PLAIN)) { + stroke(color); + strokeWeight(thickness); + line(CENTER_W + (point1[0] + 0.5) * PIXEL_SIZE, CENTER_H - (point1[1] + 0.5) * PIXEL_SIZE, CENTER_W + point2[0] * PIXEL_SIZE, CENTER_H - (point2[1] + 0.5) * PIXEL_SIZE); + } if (type.includes(AlgoType.BRZ)) { let x00, x11, y00, y11; @@ -94,58 +126,84 @@ const drawLine = (point1, point2, color = STATE.currentColor, thickness = 1, typ } }; +const drawRectangle = (point1, point2, color = STATE.currentColor, thickness = 1, type = [AlgoType.BRZ]) => { + drawLine(point1, [point2[0], point1[1]], color, 0, [AlgoType.BRZ]); + drawLine([point2[0], point1[1]], point2, color, 0, [AlgoType.BRZ]); + drawLine(point2, [point1[0], point2[1]], color, 0, [AlgoType.BRZ]); + drawLine([point1[0], point2[1]], point1, color, 0, [AlgoType.BRZ]); +}; -class LDM { - constructor() { - this.__reset(); - } - firstpoint = true; - coord; - colors; - mark = COLORS.WHAY; - - __reset = () => { - this.colors = [null, null]; - this.coord = [[0, 0], [0, 0]]; - }; - - setCoord = (i, x, y) => { - if (i === 0 && this.colors[i] !== null && !arraysEqual(this.colors[i], this.mark)) { - putPixel([this.coord[i][0], this.coord[i][1]], this.colors[i]); - } - this.colors[i] = get(mouseX, mouseY).slice(0, 3); - putPixel([x, y], this.mark); - this.coord[i] = [x, y]; - }; +const testForMask = ([x, y]) => { + let mask = [false, false, false, false]; - draw = () => { - this.colors = [STATE.currentColor, STATE.currentColor]; - drawLine(this.coord[0], this.coord[1], STATE.currentColor, 0, [AlgoType.BRZ]); + if (testXY( // top part + _SETTINGS.general.activeArea.cellBorders.left, + _SETTINGS.general.activeArea.cellBorders.right, + STATE.maskOptions.lt[1], + _SETTINGS.general.activeArea.cellBorders.top, x, y)) { + mask[0] = true } -} + if (testXY( // right part + STATE.maskOptions.rt[0], + _SETTINGS.general.activeArea.cellBorders.right, + _SETTINGS.general.activeArea.cellBorders.bottom, + _SETTINGS.general.activeArea.cellBorders.top, x, y)) { + mask[1] = true + } + if (testXY( // bottom part + _SETTINGS.general.activeArea.cellBorders.left, + _SETTINGS.general.activeArea.cellBorders.right, + _SETTINGS.general.activeArea.cellBorders.bottom, + STATE.maskOptions.rb[1], x, y)) { + mask[2] = true + } + if (testXY( // left part + _SETTINGS.general.activeArea.cellBorders.left, + STATE.maskOptions.lt[0], + _SETTINGS.general.activeArea.cellBorders.bottom, + _SETTINGS.general.activeArea.cellBorders.top, x, y)) { + mask[3] = true + } + //console.log(['top', 'right', 'bottom', 'left'].map((v, i) => mask[i] ? v : '')); + return mask; +}; const drawGrid = (drawBack = null) => { background(COLORS.BG); - _SETTINGS.general.activeArea.left = Math.floor((_SETTINGS.toolbar.width + _SETTINGS.toolbar.offset.left + _SETTINGS.toolbar.offset.right) / PIXEL_SIZE) * PIXEL_SIZE; - _SETTINGS.general.activeArea.width = Math.floor((CANVAS_W - (_SETTINGS.toolbar.offset.left + _SETTINGS.toolbar.width + _SETTINGS.toolbar.offset.right)) / PIXEL_SIZE) * PIXEL_SIZE; - _SETTINGS.general.activeArea.height = (Math.floor(CANVAS_H / PIXEL_SIZE) - 1) * PIXEL_SIZE; + + /*stroke(COLORS.BLACK); + strokeWeight(3); + console.log(_SETTINGS.general.activeArea.left, + _SETTINGS.general.activeArea.top, + _SETTINGS.general.activeArea.left + _SETTINGS.general.activeArea.width, + _SETTINGS.general.activeArea.top + _SETTINGS.general.activeArea.height); + rect( + _SETTINGS.general.activeArea.left, + _SETTINGS.general.activeArea.top, + _SETTINGS.general.activeArea.left + _SETTINGS.general.activeArea.width, + _SETTINGS.general.activeArea.top + _SETTINGS.general.activeArea.height + );*/ for (let i = _SETTINGS.general.activeArea.left; i <= _SETTINGS.general.activeArea.left + _SETTINGS.general.activeArea.width; i += PIXEL_SIZE) { - drawLineRaw([i, 0], [i, _SETTINGS.general.activeArea.height], COLORS.STROKE) + drawLineRaw([i, 0], [i, _SETTINGS.general.activeArea.height], STRCOLORS.STROKE) } for (let i = _SETTINGS.general.activeArea.top; i <= _SETTINGS.general.activeArea.height; i += PIXEL_SIZE) { - drawLineRaw([_SETTINGS.general.activeArea.left, i], [_SETTINGS.general.activeArea.width + _SETTINGS.general.activeArea.left, i], COLORS.STROKE) + drawLineRaw([_SETTINGS.general.activeArea.left, i], [_SETTINGS.general.activeArea.width + _SETTINGS.general.activeArea.left, i], STRCOLORS.STROKE) } }; _SETTINGS.setup.modules.grid.render = drawGrid; // Initializing -const putPixel = ([cox, coy], color = STATE.currentColor) => { - fill(color); - stroke(`rgba(${COLORS.STROKE.join(',')},0.9)`); - strokeWeight(1); +const putPixel = ([cox, coy], color = STATE.currentColor, ignoreMask = false) => { + if (ignoreMask || STATE.maskOptions.isActive && !testForMask([cox, coy]).includes(true) || !STATE.maskOptions.isActive) { + fill(color); + } else { + fill(COLORS.WHAY); + } + STRCOLORS.STROKE && stroke(STRCOLORS.STROKE); + STRCOLORS.STROKE ? strokeWeight(1) : strokeWeight(0); square(CENTER_W + cox * PIXEL_SIZE, CENTER_H - (coy + 1) * PIXEL_SIZE, PIXEL_SIZE); }; @@ -159,17 +217,37 @@ const startFillFrom = async (x, y, isColored = false, startColor = COLORS.WHITE, putPixel(C2Pix(x, y), STATE.currentColor); blockDir !== Directions.Left && isDrawable(C2Pix(x - PIXEL_SIZE, y)) && - await setTimeout(() => startFillFrom(x - PIXEL_SIZE, y, true, startColor, Directions.Right), 30); + await setTimeout(() => startFillFrom(x - PIXEL_SIZE, y, true, startColor, Directions.Right), _SETTINGS.toolbar.toolParams.Fill.delay); + blockDir !== Directions.Up && isDrawable(C2Pix(x, y - PIXEL_SIZE)) && + await setTimeout(() => startFillFrom(x, y - PIXEL_SIZE, true, startColor, Directions.Down), _SETTINGS.toolbar.toolParams.Fill.delay); + blockDir !== Directions.Right && isDrawable(C2Pix(x + PIXEL_SIZE, y)) && + await setTimeout(() => startFillFrom(x + PIXEL_SIZE, y, true, startColor, Directions.Left), _SETTINGS.toolbar.toolParams.Fill.delay); + blockDir !== Directions.Down && isDrawable(C2Pix(x, y + PIXEL_SIZE)) && + await setTimeout(() => startFillFrom(x, y + PIXEL_SIZE, true, startColor, Directions.Up), _SETTINGS.toolbar.toolParams.Fill.delay); + STATE.processes.terminatedByEnd++; +}; + +const forceFill = async ([x, y], ignoreColor = COLORS._MASK, blockDir = null) => { + let c = get(x, y).slice(0, 3); + if (arraysEqual(c, ignoreColor) || arraysEqual(c, COLORS.BG)) { + STATE.processes.terminatedByColor++; + return; + } + putPixel(C2Pix(x, y), COLORS.BG); + + blockDir !== Directions.Left && isDrawable(C2Pix(x - PIXEL_SIZE, y)) && + await setTimeout(() => forceFill([x - PIXEL_SIZE, y], ignoreColor, Directions.Right), _SETTINGS.toolbar.toolParams.Fill.delay); blockDir !== Directions.Up && isDrawable(C2Pix(x, y - PIXEL_SIZE)) && - await setTimeout(() => startFillFrom(x, y - PIXEL_SIZE, true, startColor, Directions.Down), 30); + await setTimeout(() => forceFill([x, y - PIXEL_SIZE], ignoreColor, Directions.Down), _SETTINGS.toolbar.toolParams.Fill.delay); blockDir !== Directions.Right && isDrawable(C2Pix(x + PIXEL_SIZE, y)) && - await setTimeout(() => startFillFrom(x + PIXEL_SIZE, y, true, startColor, Directions.Left), 30); + await setTimeout(() => forceFill([x + PIXEL_SIZE, y], ignoreColor, Directions.Left), _SETTINGS.toolbar.toolParams.Fill.delay); blockDir !== Directions.Down && isDrawable(C2Pix(x, y + PIXEL_SIZE)) && - await setTimeout(() => startFillFrom(x, y + PIXEL_SIZE, true, startColor, Directions.Up), 30); + await setTimeout(() => forceFill([x, y + PIXEL_SIZE], ignoreColor, Directions.Up), _SETTINGS.toolbar.toolParams.Fill.delay); STATE.processes.terminatedByEnd++; }; const changeColor = (type) => { + if (STATE.colorBlocked) return; switch (type) { case LEFT: _SETTINGS.general.color.current = @@ -190,50 +268,80 @@ const changeColor = (type) => { const ToolsRenderer = { 'Pixel': (x, y, sx, sy) => { fill(50); - text('Pix', x, y, sx, sy); + text('Pix', x+1, y, sx-1, sy); }, 'Line': (x, y, sx, sy) => { fill(50); - text('Line', x, y, sx, sy); + text('Line', x+1, y, sx-1, sy); + }, + 'Rectangle': (x, y, sx, sy) => { + fill(50); + text('Rect', x+1, y, sx-1, sy); + }, + 'Polygon': (x, y, sx, sy) => { + fill(50); + text('Poly', x+1, y, sx-1, sy); }, 'Fill': (x, y, sx, sy) => { fill(50); - text('Fill', x, y, sx, sy); + text('Fill', x+1, y, sx-1, sy); }, 'Colorizer': (x, y, sx, sy) => { fill(STATE.currentColor); rect(x, y, sx, sy); fill(50); - text('COL', x, y, sx, sy); + text('COL', x+1, y, sx-1, sy); }, 'Clear': (x, y, sx, sy) => { fill(230, 40, 40); - text('ERS', x, y, sx, sy); + text('ERS', x+1, y, sx-1, sy); }, 'Export': (x, y, sx, sy) => { fill(50); - text('SAV', x, y, sx, sy); - } + text('SAV', x+1, y, sx-1, sy); + }, + 'Delimiter': (x, y, sx, sy) => { + stroke(STRCOLORS.STROKEBLACKA); + line(x, y + sy/2, x + sx, y + sy/2); + }, + 'Masking': (x, y, sx, sy) => { + fill(COLORS.RED); + text('MSK', x+1, y, sx-1, sy); + }, + 'MaskActivate': (x, y, sx, sy) => { + fill(STATE.maskOptions.isActive ? COLORS._MASK : COLORS.WHAY); + rect(x, y, sx, sy); + fill(50); + text(STATE.maskOptions.isActive ? 'ON' : 'OFF', x+1, y, sx-1, sy); + }, }; const ToolActions = { - 'Pixel': (type) => {STATE.activeTool = 'Pixel'}, - 'Line': (type) => {STATE.activeTool = 'Line'}, - 'Fill': (type) => {STATE.activeTool = 'Fill'}, - 'Colorizer': (type) => {changeColor(type)}, - 'Clear': (type) => { + 'Pixel': (type) => ToolManager.changeTool('Pixel'), + 'Line': (type) => ToolManager.changeTool('Line'), + 'Rectangle': (type) => ToolManager.changeTool('Rectangle'), + 'Polygon': (type) => ToolManager.changeTool('Polygon'), + 'Fill': (type) => ToolManager.changeTool('Fill'), + 'Colorizer': (type) => ToolManager.useCallback(() => changeColor(type)), + 'Clear': (type) => ToolManager.useCallback( () => { Object.keys(_SETTINGS.setup.modules).forEach(key => { _SETTINGS.setup.modules[key].model ? _SETTINGS.setup.modules[key].model.render() : (_SETTINGS.setup.modules[key].render ? _SETTINGS.setup.modules[key].render() : console.log(`${key} isn\'t inited before clearing`)) }); document.dispatchEvent(new Event('clearEvent')); - }, - 'Export': (type) => { - saveCanvas(createCanvasName(), 'jpg') - } + }), + 'Export': (type) => ToolManager.useCallback( + () => saveCanvas(createCanvasName(), 'jpg') + ), + 'Delimiter': (type) => {}, + 'Masking': (type) => ToolManager.changeTool('Masking'), + 'MaskActivate': (type) => ToolManager.useCallback( + () => STATE.maskOptions.isActive = !STATE.maskOptions.isActive + ), }; + class Toolbar { constructor() { _SETTINGS.setup.modules.toolbar.isActive = true; @@ -308,7 +416,9 @@ class Toolbar { callback: ToolActions[tool] }); - rect(px, py, sx, sy); + if (tool !== 'Delimiter') { + rect(px, py, sx, sy); + } ToolsRenderer[tool](px, py, sx, sy); py += this._margins.top + this._blockSize.height; diff --git a/core/localCrud.js b/core/localCrud.js new file mode 100644 index 0000000..bcf42d8 --- /dev/null +++ b/core/localCrud.js @@ -0,0 +1,102 @@ +const CrudStore = { + Pixels: 'store-pixels', + Lines: 'store-lines', + Rectangles: 'store-rects', + Polygons: 'store-polygons', + Temporaries: 'store-temporaries', + Mask: 'store-mask', + State: 'store-state-saver', +}; + +class StorageAssistant { + op = window.localStorage; + + // get(key: string, expectedValue: string | null) -> realValue || expectedValue[string | null] + get = (key, expectedValue) => { + let rawValue = this.op.getItem(key), value; + try { + value = JSON.parse(rawValue); + } catch (e) { + value = rawValue; + } + return value || expectedValue + }; + + // set(key: string, value: string) -> void + set = (key, value) => { + if (typeof value !== "string") value = JSON.stringify(value); + this.op.setItem(key, value); + }; + + // delete(key: string) -> void + delete = (key) => { + this.op.removeItem(key) + }; + + // _clear() -> [Native].clear + _clear = this.op.clear; +} + +class Crud { + storeName = null; + prefix = 'CrudStorage__'; + sa = new StorageAssistant(); + state = { + value: [], + }; + + constructor(store) { + if (!store) { + throw new Error('There\'s no Store provided to Crud constructor! (E: emp_constructor_called)'); + } + if (typeof store !== 'string') { + throw new Error('Incorrect Store name provided to Crud constructor! (E: incorrect_store_name)'); + } + this.storeName = this.prefix + store; + console.log('Crud::Build :', this.storeName); + if (!EventManager.findListeners(this.storeName).length) { + //console.log('There\'s no Initialization found. Initializing manually...'); + this.init(); + } + } + + + init = () => { + + console.log('Crud::Init :', this.storeName); + + EventManager.addListener(this.storeName, this.eventHandler, 'crud-init'); + //document.addEventListener(this.storeName + '_update', this.eventHandler); + this.__purgeData(); + }; + + create = (iObjectAny) => { + let temp = this.sa.get(this.storeName, null) || this.state; + console.log(temp); + temp.value.push(iObjectAny); + this.sa.set(this.storeName, temp); + EventManager.dispatchEvent(this.storeName, {"type": 'add'}, 'crud-create'); + //document.dispatchEvent(new CustomEvent(this.storeName + '_update', {"detail": {"type": 'add'}})); + }; + + update = (iGeneralObjectAny) => { + this.sa.set(this.storeName, iGeneralObjectAny); + EventManager.dispatchEvent(this.storeName, {"type": 'update'}, 'crud-update'); + //document.dispatchEvent(new CustomEvent(this.storeName + '_update', {"detail": {"type": 'update'}})); + }; + + getAll = () => { + return this.sa.get(this.storeName, null) || this.state; + }; + + eventHandler = (event) => { + // redraw()???????? + console.log(`UPDATE: ${event.type} with`, event.detail); + }; + + __purgeData = () => { + console.log('purging', this.storeName, '...'); + this.sa.delete(this.storeName) + } + +} diff --git a/core/settings.js b/core/settings.js index 5ed337f..88b872c 100644 --- a/core/settings.js +++ b/core/settings.js @@ -1,6 +1,6 @@ let FRAME_RATE = 60; -let PIXEL_SIZE = 15; -let CANVAS_W = 1150; +let PIXEL_SIZE = 14; +let CANVAS_W = 1300; let CANVAS_H = 930; let CANVAS_HC = Math.round(CANVAS_H / PIXEL_SIZE); let CANVAS_WC = Math.round(CANVAS_W / PIXEL_SIZE); @@ -12,9 +12,14 @@ const COLORS = { BLACK: [0, 0, 0], WHITE: [255, 255, 255], WHAY: [190, 190, 190], + GRAY: [193, 193, 193], + DARKGRAY: [140, 140, 140], + LIGHTBLACK: [40, 40, 40], + + _MASK: [230, 170, 190], + RED: [255, 110, 110], YELLOW: [255, 246, 138], - GREEN: [138, 255, 154], BLUE: [62, 78, 235], CYAN: [99, 244, 246], @@ -24,14 +29,18 @@ const COLORS = { DARKGREEN: [3, 88, 30], ORANGE: [255, 138, 28], - GRAY: [193, 193, 193], - DARKGRAY: [140, 140, 140], - LIGHTBLACK: [40, 40, 40], STROKE: [193, 193, 193], BG: null, // to be defined }; COLORS.BG = COLORS.WHITE; +const STRCOLORS = { + STROKEBLACKA: `rgba(0,0,0,0.3)`, + STROKEWHITEA: `rgba(255,255,255,0.3)`, + STROKE: undefined, +}; +STRCOLORS.STROKE = STRCOLORS.STROKEBLACKA; + const Directions = { Up: 1, Left: 2, @@ -42,21 +51,27 @@ const Directions = { const Tools = { Pixel: 'Pixel', Line: 'Line', + Rectangle: 'Rectangle', + Polygon: 'Polygon', Fill: 'Fill', Colorizer: 'Colorizer', Clear: 'Clear', Export: 'Export', + Delimiter: 'Delimiter', + Masking: 'Masking', + MaskActivate: 'MaskActivate', }; const AlgoType = { BRZ: 0, + PLAIN: 9, }; let _SETTINGS = { toolbar: { width: 60, - height: 280, + height: 600, offset: { top: 30, left: 15, @@ -77,7 +92,24 @@ let _SETTINGS = { top: 10, left: 2, }, - toolset: [Tools.Pixel, Tools.Line, Tools.Fill, Tools.Colorizer, Tools.Clear, Tools.Export], + toolset: [ + Tools.Pixel, + Tools.Line, + Tools.Rectangle, + Tools.Polygon, + Tools.Fill, + Tools.Colorizer, + Tools.Delimiter, + Tools.Masking, + Tools.MaskActivate, + Tools.Clear, + Tools.Delimiter, + Tools.Export], + toolParams: { + Fill: { + delay: 0, + } + } }, general: { activeArea: { @@ -140,6 +172,9 @@ let _SETTINGS = { model: null, }, }, + system: { + logEnabled: true, + }, initialState: { activeTool: 'Line', activeRegion: null, @@ -148,7 +183,17 @@ let _SETTINGS = { created: 0, terminatedByColor: 0, terminatedByEnd: 0, - } + }, + operationCounter: 0, + lastClickedColor: null, + maskOptions: { + lt: [], + rt: [], + lb: [], + rb: [], + isActive: false, + }, + colorBlocked: false, } } }; @@ -167,3 +212,9 @@ const _SettingsManager = new SettingsManager(); _SETTINGS.setup.initialState.currentColor = _SETTINGS.general.color.palette[_SETTINGS.general.color.current]; +if (!_SETTINGS.setup.system.logEnabled) { + console.log = (a) => { + return; + } +} + diff --git a/core/sketch.js b/core/sketch.js index 5c346ed..159383d 100644 --- a/core/sketch.js +++ b/core/sketch.js @@ -10,16 +10,20 @@ document.addEventListener("keydown", event => { handleHotkey(event.key); // do something }); +document.addEventListener("color-block", event => { + //console.log(event); + STATE.colorBlocked = event.detail.value; +}); + -const ldm = new LDM(); +const ldm = new LDM(PolyMan); const tbar = new Toolbar(); let [x, y] = [0, 0]; function setup() { createCanvas(CANVAS_W, CANVAS_H); frameRate(FRAME_RATE); - //console.log("Setup completed"); - //console.log(CANVAS_HC, CANVAS_WC); + countSizes(); STATE.activeTool = Tools.Line; STATE.currentColor = COLORS.RED; @@ -38,6 +42,14 @@ function setup() { STATE.activeTool = Tools.Fill; tbar.render(); }, + 'r': () => { + STATE.activeTool = Tools.Rectangle; + tbar.render(); + }, + 'm': () => { + STATE.activeTool = Tools.Masking; + tbar.render(); + }, 'c': () => { ToolActions[Tools.Colorizer](LEFT); tbar.render(); @@ -53,6 +65,10 @@ function setup() { draw(); tbar.render(); setInterval(utilsFixedUpdate, 50); + + // Debug: painting area size in cells + // console.log(_SETTINGS.general.activeArea.cellBorders); + noLoop(); } @@ -65,6 +81,8 @@ function mouseReleased() { case 'activeArea': switch (STATE.activeTool) { case 'Line': + case 'Rectangle': + case 'Masking': if (mouseButton === LEFT) { ldm.setCoord(0, x, y); } @@ -73,6 +91,25 @@ function mouseReleased() { ldm.draw(); } break; + case 'Polygon': + if (mouseButton === LEFT) { + ldm.setCoord(ldm.firstpoint ? 0 : 1, x, y); + if (!ldm.firstpoint) { + ldm.draw().then(() => ldm.setCoord(0, x, y)); + } else { + ldm.firstpoint = false; + } + /* + if (figure_closed) { + ldm.firstpoint = true; + } + */ + } + if (mouseButton === RIGHT) { + ldm.setCoord(0, x, y); + ldm.firstpoint = false; + } + break; case 'Fill': if (mouseButton === LEFT) { let sc = get(mouseX, mouseY).slice(0, 3); @@ -97,6 +134,7 @@ function mouseReleased() { function draw() { [x, y] = C2Pix(mouseX, mouseY); + //console.log(x, y); switch (STATE.activeRegion) { case 'activeArea': diff --git a/core/utils.js b/core/utils.js index 185e9a5..aacc7cd 100644 --- a/core/utils.js +++ b/core/utils.js @@ -22,6 +22,14 @@ const CAr2Pix = (coords = [0, 0]) => { return [Math.round((coords[0] - CENTER_W) / PIXEL_SIZE - 0.5), -Math.round((coords[1] - CENTER_H) / PIXEL_SIZE + 0.5)]; }; +const Pix2C = (coords = [0, 0]) => { + //console.log('requested', coords); + return [ + CANVAS_W / 2 + (coords[0] * PIXEL_SIZE), + CANVAS_H / 2 - PIXEL_SIZE / 2 - (coords[1] * PIXEL_SIZE) + ]; +}; + function arraysEqual(a, b) { return Array.isArray(a) && Array.isArray(b) && @@ -46,3 +54,239 @@ const isDrawable = (coords) => { _SETTINGS.general.activeArea.cellBorders.bottom, _SETTINGS.general.activeArea.cellBorders.top, coords[0], coords[1]); }; + +const intersectLines = ({from: [x1, y1], to: [x2, y2]}, {from: [x3, y3], to: [x4, y4]}) => { + let ua, ub, denom = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1); + if (denom === 0) { + return null; + } + ua = ((x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3))/denom; + ub = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3))/denom; + return { + x: x1 + ua * (x2 - x1), + y: y1 + ua * (y2 - y1), + seg1: ua >= 0 && ua <= 1, + seg2: ub >= 0 && ub <= 1, + }; +}; + +const getMaskIterableLines = () => { + return [ + {from: STATE.maskOptions.lt, to: STATE.maskOptions.rt}, + {from: STATE.maskOptions.rt, to: STATE.maskOptions.rb}, + {from: STATE.maskOptions.rb, to: STATE.maskOptions.lb}, + {from: STATE.maskOptions.lb, to: STATE.maskOptions.rt}, + ] +}; + +class MaskClass { + constructor() { + this.__reset(); + document.addEventListener('Mask', this.eventHandler); + }; + + eventHandler = event => { + console.log(event.detail); + if (event.detail.data) { + EventManager.dispatchEvent(event.detail.initiator, { + data: this.testPolygon(event.detail.data) + }, 'polygon-closed'); + } + }; + + __reset = () => { + }; + + testPolygon = (path) => { + console.log(path); + let ins = []; + path.forEach((el) => { + getMaskIterableLines().forEach((maskline) => { + let d = intersectLines(el, maskline) || {seg1: false}; + console.log(d); + if (d.seg1 && d.seg2) { + ins.push([Math.round(d.x - 0.5) + 1, Math.round(d.y)]); + } + }) + }); + return ins; + } +} +let Mask = new MaskClass(); + +class PolygonContainer { + constructor() { + this.__reset(); + } + globalPolygonContainer = []; + storage = []; + isClosed = true; + firstPointCoords = []; + polygonCrud = new Crud(CrudStore.Polygons); + + __reset = () => { + this.storage = []; + this.isClosed = true; + EventManager.dispatchEvent('color-block', {value: false}, 'polygon-closed'); + EventManager.addListener('tool-change', this.eventHandler, 'handle-tool-in-poly'); + }; + + eventHandler = event => { + if (event.detail.value && !this.isClosed) { + console.log(this.storage[this.storage.length - 1].to, this.firstPointCoords); + EventManager.dispatchEvent('LDM', { + type: 'drawRaw', data: { + from: this.storage[this.storage.length - 1].to, + to: this.firstPointCoords.slice(), + } + }, 'polygon-closed'); + this.addSegment(this.storage[this.storage.length - 1].to, this.firstPointCoords.slice()); + + } + }; + + addSegment = ([xs, ys], [xe, ye]) => { + if (this.storage.length === 0) { + this.firstPointCoords = [xs, ys]; + this.isClosed = false; + STATE.colorBlocked = true; + EventManager.dispatchEvent('color-block', {value: true}, 'polygon-first-point'); + } + this.storage.push({from: [xs, ys], to: [xe, ye]}); + if (arraysEqual([xe, ye], this.firstPointCoords)) { + putPixel([this.firstPointCoords[0], this.firstPointCoords[1]]); + this.closeFigure(); + } + }; + + oneTimeEventHandler = event => { + if (event.detail.data) { + console.log(event.detail.data); + event.detail.data.forEach((el) => { + putPixel(el, COLORS.BLACK, true); + }); + } + }; + + closeFigure = () => { + console.log('Figure Closed'); + this.globalPolygonContainer.push({name: 'Polygon', path: this.storage}); + if (STATE.maskOptions.isActive) { + EventManager.addListener('polygon-closed-masker', this.oneTimeEventHandler, 'handle-mask-test-result'); + EventManager.dispatchEvent('Mask', {data: this.storage}, 'polygon-closed-masker'); + + } + this.storage = []; + this.polygonCrud.update(this.globalPolygonContainer); + this.isClosed = true; + EventManager.dispatchEvent('color-block', {value: false}, 'polygon-closed'); + EventManager.dispatchEvent('LDM', {type: 'reset'}, 'polygon-closed'); + } +} + +class LDM { + firstpoint = true; + coord; + colors; + mark = COLORS.WHAY; + polygonManager = null; + + constructor(polyMan) { + this.__reset(); + if (!polyMan) { + throw new Error('No Polygon Manager provided to LineDrawingModule'); + } + this.polygonManager = polyMan; + + document.addEventListener('LDM', this.eventHandler); + } + + __reset = () => { + this.colors = [null, null]; + this.coord = [[0, 0], [0, 0]]; + this.firstpoint = true; + this.polygonManager?.__reset(); + }; + + eventHandler = (event) => { + if (event.detail.type) { + switch (event.detail.type) { + case 'reset': + this.__reset(); + break; + case 'drawRaw': + console.log(event.detail); + this.__drawRaw(event.detail.data); + break; + } + } + }; + + setCoord = (i, x, y) => { + if (i === 0 && this.colors[i] !== null && !arraysEqual(this.colors[i], this.mark)) { + putPixel([this.coord[i][0], this.coord[i][1]], this.colors[i]); + } + + this.colors[i] = get(mouseX, mouseY).slice(0, 3); + putPixel([x, y], this.mark); + this.coord[i] = [x, y]; + }; + + __drawRaw = async (data) => { + console.log('low-level event-driven draw in LDM called', data.from, data.to); + drawLine(data.from, data.to, STATE.currentColor, 0, [AlgoType.BRZ]); + }; + + draw = async () => { + this.colors = [STATE.currentColor, STATE.currentColor]; + switch (STATE.activeTool) { + case 'Line': + drawLine(this.coord[0], this.coord[1], STATE.currentColor, 0, [AlgoType.BRZ]); + break; + case 'Rectangle': + drawRectangle(this.coord[0], this.coord[1], STATE.currentColor, 0, [AlgoType.BRZ]); + break; + case 'Polygon': + drawLine(this.coord[0], this.coord[1], STATE.currentColor, 0, [AlgoType.BRZ]); + this.polygonManager.addSegment(this.coord[0], this.coord[1]); + break; + case 'Masking': + drawRectangle(this.coord[0], this.coord[1], COLORS._MASK, 0, [AlgoType.BRZ]); + STATE.maskOptions.lt = [this.coord[0][0], this.coord[0][1]]; + STATE.maskOptions.rt = [this.coord[1][0], this.coord[0][1]]; + STATE.maskOptions.lb = [this.coord[1][0], this.coord[1][1]]; + STATE.maskOptions.rb = [this.coord[0][0], this.coord[1][1]]; + this.__reset(); + break; + } + } +} + +class ToolManagerClass { + constructor() { + console.log('ToolManager created') + } + changeTool = (toolName) => { + STATE.activeTool = toolName; + EventManager.dispatchEvent('tool-change', {value: true}, 'tool-changed'); + }; + useCallback = callbackFn => callbackFn(); +} +let ToolManager = new ToolManagerClass(); + +class EventManagerClass { + _storage = []; + getFormat = (eve, initiator, payload) => { + return {eve, initiator, payload} + }; + addListener = (eve, handler, purpose) => { + document.addEventListener(eve, handler); + this._storage.push({event: eve, handler, purpose}) + }; + dispatchEvent = (eve, payload, initiator) => + document.dispatchEvent(new CustomEvent(eve, {"detail": {...payload, initiator}})); + findListeners = (eve) => { + return this._storage.filter(el => el.event === eve) + } +} +let EventManager = new EventManagerClass(); diff --git a/index.html b/index.html index 3077664..d2a3527 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ +