// ui divs / doms / and tigers import DomTools from './vdom.js' function Floater(view, grouptype) { // want one, // altho, non-ideal ... let dt = new DomTools(view) // TODO: /* ... think ... think ... -> button to expand, and gather? div-related? -> button drags as well ? titlematter does hover, not drag, two funcitons ? -> !!!!!!!!!!!!! button for fixed ! fixed !!!!!!!!!!!!!!! -> roll sim back, so that fixed / unfixed is meaningful -> explosions afterwards */ // a floater is an individual that can be dragged, fixed/unfixed, etc // it is exactly the thing we give to d3 to lay out this.x = 0 this.y = 0 // want these, this.w = 101 this.h = 101 // if it has fx, fy, it is fixed this.r = 101 // to default, // these are handy, this.bb = {} this.bb.x1 = -50 this.bb.y1 = -50 this.bb.x2 = 51 this.bb.y2 = 51 // by default, no this.isFixed = false // will have this to track within simulation links this.simIndex = null // every floater has *at least* this ... // this is where we put the buttonz this.ownElement = $(`<div>`).addClass('float').get(0) $(this.ownElement).append(`<i class="em em-pushpin"></i>`) $(this.ownElement).on('click', (evt) => { if (this.isFixed === true) { this.free() } else { this.fix() } }).on('mouseover', (evt) => { document.body.style.cursor = 'pointer' }).on('mouseout', (evt) => [ document.body.style.cursor = 'auto' ]) $(view.plane).append(this.ownElement) // fixed <i class="em em-round_pushpin"></i> // open <i class="em em-pushpin"></i> // this is a list of elements to carry about this.bag = [] // gets {element, x, y} // fuuu let writeTitleMatter = (div, def) => { let md = false div.onmouseover = () => { def.highlight() if (!md) document.body.style.cursor = 'grab' } div.onmouseout = () => { def.unhighlight() if (!md) document.body.style.cursor = 'auto' } div.onmousedown = (evt) => { md = true document.body.style.cursor = 'grabbing' // pin to current position, this.fix() evt.preventDefault() evt.stopPropagation() let domElemMouseMove = (evt) => { // TRANSFORMS here to move div about on drag evt.preventDefault() evt.stopPropagation() // need to find that top-level view's scale transform this.fx += evt.movementX / view.tls this.fy += evt.movementY / view.tls this.newFix() this.tick() } function rmOnMouseUp(evt) { md = false document.body.style.cursor = 'auto' document.removeEventListener('mousemove', domElemMouseMove) document.removeEventListener('mouseup', rmOnMouseUp) } document.addEventListener('mousemove', domElemMouseMove) document.addEventListener('mouseup', rmOnMouseUp) } } // -------------------------------------------- TAKE // take up new things this.typeset = [] this.take = (div, def, stall, resize) => { let type = '' // cover view-containing ... ?? // if ($(div).is('.native') && $(div).children().contains('.view')) let item = { type: '', de: div, def: def, offsetX: 0, offsetY: 0 } // always nice to do this first, // defs -> the dom $(view.plane).append(item.de) // only a few cases we will allow, if ($(div).is('.inputs')) { item.type = 'inputs' } else if ($(div).is('.outputs')) { item.type = 'outputs' } else if ($(div).is('.defcore')) { item.type = 'core' } else if ($(div).is('.nativewrap')) { item.type = 'native' // at the gitgo, set this to our current width... // this width thing is happening because you're rewriting the // so ... not a take, a swap ? !!! FFUUUUU // native element's wrapper ... this.calculateSizes() if (this.w < 200) { $(item.de).css('width', `200px`) } else { $(item.de).css('width', `${this.w}px`) } // and for non-spec'd heights, if (item.de.clientHeight < 10) { $(item.de).css('height', '200px') } // ok then, resizeable also dt.makeResizeable(item.de, this.onElementResize, resize) } else if ($(div).is('.defbutton')) { item.type = 'button' } else { // best guess, but.. item.type = 'core' console.error('no thx, no recognized type when taking div into floater') } // put it in the baaaag this.typeset.push(item.type) this.bag.push(item) // (also) write the drag content, if there's a header on board let header = $(div).children('.header').get(0) if (header) { writeTitleMatter(header, item.def) } // just want to do this once, if (stall !== true) this.onChange() } // end take this.fitNativeToFloater = () => { this.calculateSizes(true) // find it, let nt = this.bag.find((cnd) => { return cnd.type === 'native' }) if (!nt) { console.error('not finding a native element to resize here') } else { $(nt.de).css('width', `${this.w}px`) // want also to reset the resizing handle ... // kind of messy call, but let w = nt.de.clientWidth let h = nt.de.clientHeight let handle = $(nt.de).children('.resizehandle').get(0) dt.writeTransform(handle, { x: w, y: h }) } } // to catch, this.onElementResize = () => { // this is a lot, this.onChange() // our parent, view.kick() } // GROUP TYPES: // 'edges' ... (core) with (outputs) and .edges || (inputs) // 'unwrapped' ... (core) with (outputs) and .unwrapped || (inputs) // 'wrapped' ... (core) with (inputs) and (outputs) and (native .view) and .wrapped // 'std' ... (core) with some set of (inputs) and (outputs) and (native) this.makeEdges = () => { this.grouptype === 'edges' this.onChange() } // -------------------------------------------- CHANGE this.grouptype = grouptype // don't want to do this all the time // to 'open' ... put core with outputs, // to 'unwrap' ... put core with inputs, it will go below this.onChange = () => { // find types, let core = this.bag.find((cnd) => { return cnd.type === 'core' }) let inputs = this.bag.find((cnd) => { return cnd.type === 'inputs' }) let outputs = this.bag.find((cnd) => { return cnd.type === 'outputs' }) let native = this.bag.find((cnd) => { return cnd.type === 'native' }) // possible to have many, let buttons = [] for (let item of this.bag) { if (item.type === 'button') buttons.push(item) } let spaces = 5 // TYPE CASES BY TYPESET //console.log('FLT onchange, floater has', core ? '(core)' : '', inputs ? '(inputs)' : '', outputs ? '(outputs)' : '', native ? '(native)' : '') if (this.grouptype === 'std') { // 'std' ... (core) with some set of (inputs) and (outputs) and (native) core.offsetX = -core.de.clientWidth core.offsetY = 0 if (inputs) { inputs.offsetX = core.offsetX - inputs.de.clientWidth - spaces - 2 inputs.offsetY = 0 } if (outputs) { outputs.offsetX = spaces outputs.offsetY = 0 } if (native) { // calc sizes, ignoring native this.calculateSizes(true) native.offsetX = this.bb.x1 native.offsetY = this.bb.y2 + spaces // for resizing, if (outputs) { let gap = native.de.clientWidth - this.w if (gap > 1) { outputs.offsetX += gap } } } } else if (this.grouptype === 'unwrapped') { // 'unwrapped' ... (core) with (outputs) and .unwrapped || (inputs) if (core) { // unwrapped, left side, core, outputs, etc core.offsetX = -core.de.clientWidth core.offsetY = 0 if (outputs) { outputs.offsetX = spaces outputs.offsetY = 0 } if (native) { this.calculateSizes(true) native.offsetX = this.bb.x1 native.offsetY = this.bb.y2 + spaces if (outputs) { let gap = native.de.clientWidth - this.w if (gap > 1) { outputs.offsetX += gap } } } } else { // unwrapped, right side, it's the lonely inputs if (inputs) { inputs.offsetX = -inputs.de.clientWidth inputs.offsetY = 0 } } } else if (this.grouptype === 'wrapped') { // should have 'em all let's just wyld out // the view attached is king, let ntw = native.de.clientWidth let nth = native.de.clientHeight // left ... native.offsetX = -core.de.clientWidth native.offsetY = core.de.clientHeight + spaces // core core.offsetX = -core.de.clientWidth core.offsetY = 0 // inputs, well left inputs.offsetX = core.offsetX - inputs.de.clientWidth - spaces inputs.offsetY = $(inputs.de).children('.header').get(0).clientHeight + spaces + native.offsetY // ouputs, as is tradition outputs.offsetX = ntw + spaces + core.offsetX outputs.offsetY = inputs.offsetY } else if (this.grouptype === 'edges') { if(core && outputs){ this.edgetype = 'left' // and on top of self, core.offsetX = -core.de.clientWidth core.offsetY = -core.de.clientHeight - 25 outputs.offsetX = core.offsetX outputs.offsetY = core.offsetY - outputs.de.clientHeight - spaces } else { this.edgetype = 'right' inputs.offsetX = -inputs.de.clientWidth inputs.offsetY = -inputs.de.clientHeight - 25 // aaand view.msgbox.setTopMargin( - inputs.offsetY + 20) } } else { console.error(`this floater grouptype not written or recognized: '${this.grouptype}'`) } // now we can cover buttons, using core offsets // cover buttons, if(buttons.length){ if (buttons.length > 0) { if (!core) console.error('watch buttons not-on core?') let inc = 0 for (let btn of buttons) { btn.offsetX = core.offsetX + inc btn.offsetY = -20 - core.offsetY inc += 20 } } // ok, // now we can execute this, this.moveTo() // oy oy oy } // end onChange let writePosition = (element, x, y) => { $(element).css('left', `${x}px`).css('top', `${y}px`) } let readPosition = (element) => { let it = $(element) return { x: parseInt(it.css('left')), y: parseInt(it.css('top')) } } let readSize = (element) => { return { w: element.clientWidth, h: element.clientHeight } } this.moveTo = (x, y, backdoor) => { // but still probably want to check our offsets, if (this.isFixed && !backdoor) { this.x = this.fx this.y = this.fy } // to just draw new position, run with no args if (x === undefined) { x = this.x y = this.y } else { this.x = x this.y = y } // will return this ... let desire = { x: 0, y: 0 } // check bounds, if(!view.isTopLevel){ // bummer to do so many times ! let bounds = view.getCurrentBounds() this.calculateSizes() // if(this.grouptype === 'edges'){ //console.log('bounds', bounds) // x1, y1, x2, y2, w, h if(this.edgetype === 'left'){ this.x = - this.bb.x1 this.y = - this.bb.y1 + 17 + 5 } else { this.x = bounds.x2 - this.bb.x2 - 2 this.y = bounds.y1 - this.bb.y1 + 17 + 5 } } else { //console.log('place bounds', this.bb) //console.log('bounds', bounds) // x needs to contend with offset, // this.bb.x1 probably -ve, // this.bb.y1 probably -ve, // want to track, but won't push things left or up, this.x = Math.max(bounds.x1 - this.bb.x1, this.x) // lower bounds this.y = Math.max(bounds.y1 - this.bb.y1, this.y) // want-right term, let rbnd = bounds.x2 - this.bb.x2 if(this.x > rbnd){ desire.x = this.x - rbnd this.x = rbnd // wants to go right } // want-down term, let lbnd = bounds.y2 - this.bb.y2 if(this.y > lbnd){ desire.y = this.y - lbnd this.y = lbnd // wants to go down } } } // do work writePosition(this.ownElement, x - 16.5, y - 20) for (let item of this.bag) { writePosition(item.de, this.x + item.offsetX, this.y + item.offsetY) } // aaaand tell em what we waaaaant return desire } this.newFix = () => { this.moveTo(this.fx, this.fy, true) } // as is tradition, we'll keep things local, // and just make sure we re-calculate during relevant events... ? this.calculateSizes = (nonNative) => { // from core of xy, values like // // * -------------- (y1) -------- * // | | | // |(x1) --------- (x,y) ---- (x2)| // | | | // | | | // * ------------ (y2) ---------- * let x1 = 0 let y1 = 0 let x2 = 0 let y2 = 0 for (let item of this.bag) { // items are str8 cut dom elements, if (nonNative && item.type === 'native') continue let wt = readSize(item.de) // we know item offsets, so if (item.offsetX < x1) x1 = item.offsetX if (item.offsetY < y1) y1 = item.offsetY if (item.offsetX + wt.w > x2) x2 = item.offsetX + wt.w if (item.offsetY + wt.h > y2) y2 = item.offsetY + wt.h // corners } this.bb.x1 = x1 this.bb.y1 = y1 this.bb.x2 = x2 this.bb.y2 = y2 // ok, this.w = x2 - x1 this.h = y2 - y1 // also, this.r = Math.max(this.w, this.h) / 2 // check, let aspect = this.w / this.h if (aspect > 8 || aspect < 0.125) { //console.error(`warning: deviously large aspect for 'circular' collisions: ${aspect}, logging floater`, this) } } this.tick = () => { // will use to bother the simulation view.tick() } this.free = () => { this.x = this.fx this.y = this.fy delete this.fx delete this.fy this.isFixed = false $(this.ownElement).html(`<i class="em em-pushpin"></i>`) this.tick() } this.fix = () => { $(this.ownElement).html(`<i class="em em-round_pushpin"></i>`) this.isFixed = true this.fx = this.x this.fy = this.y this.moveTo(this.fx, this.fy, true) } this.fixTo = (x, y) => { this.x = x this.y = y this.fix() } // RIP this.cleanup = () => { // js deletes things that are unreachable, so // there are a few objects that are on the canvas we want to get rid of $(this.ownElement).remove() // also, any handle added to a native element // not being able to search by js subset is kind of a bummer here, but let native = this.bag.find((cnd) => { return cnd.type === 'native' }) if (native) { $(native.de).children('.resizehandle').remove() } } // } export default Floater