Select Git revision
1_Three.js.zip
-
Amira Abdel-Rahman authoredAmira Abdel-Rahman authored
vfloater.js 15.27 KiB
/*
view/vfloater.js
Jake Read at the Center for Bits and Atoms
(c) Massachusetts Institute of Technology 2019
This work may be reproduced, modified, distributed, performed, and
displayed for any purpose, but must acknowledge the squidworks and cuttlefish projects.
Copyright is retained and must be preserved. The work is provided as is;
no warranty is provided, and users accept all liability.
*/
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