import { html } from './html'
const DEFAULT_COLOR = '#a8f0f4'
const DEFAULT_SIZE = 10
const SnapOptionsDefault = {
screen: true,
windows: true,
snap: 20,
color: DEFAULT_COLOR,
spacing: 5,
indicator: DEFAULT_SIZE
}
export class Snap
{
/**
* add edge snapping plugin
* @param {WindowManager} wm
* @param {object} [options]
* @param {boolean} [options.screen=true] snap to screen edges
* @param {boolean} [options.windows=true] snap to window edges
* @param {number} [options.snap=20] distance to edge in pixels before snapping and width/height of snap bars
* @param {string} [options.color=#a8f0f4] color for snap bars
* @param {number} [options.spacing=5] spacing distance between window and edges
* @param {number} [options.indicator=10] size in pixels of snapping indicator (the indicator is actually twice the size of what is shown)
*/
constructor(wm, options={})
{
this.wm = wm
this.options = Object.assign({}, SnapOptionsDefault, options)
this.highlights = html({ parent: this.wm.overlay, styles: { 'position': 'absolute' } })
this.horizontal = html({
parent: this.highlights, styles: {
display: 'none',
position: 'absolute',
height: `${this.options.indicator}px`,
borderRadius: `${this.options.indicator}px`,
backgroundColor: this.options.color
}
})
this.vertical = html({
parent: this.highlights, styles: {
display: 'none',
position: 'absolute',
width: `${this.options.indicator}px`,
borderRadius: `${this.options.indicator}px`,
backgroundColor: this.options.color
}
})
this.horizontal
this.showing = []
}
stop()
{
this.highlights.remove()
this.stopped = true
}
addWindow(win)
{
win.on('move', () => this.move(win))
win.on('move-end', () => this.moveEnd(win))
}
screenMove(rect, horizontal, vertical)
{
const width = document.body.clientWidth
const height = document.body.clientHeight
if (rect.left - this.options.snap <= width && rect.right + this.options.snap >= 0)
{
if (Math.abs(rect.top - 0) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.top - 0), left: 0, width, top: 0, side: 'top', screen: true })
}
else if (Math.abs(rect.bottom - height) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.bottom - height), left: 0, width, top: height, side: 'bottom', screen: true })
}
}
if (rect.top - this.options.snap <= height && rect.bottom + this.options.snap >= 0)
{
if (Math.abs(rect.left - 0) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.left - 0), top: 0, height, left: 0, side: 'left', screen: true })
}
else if (Math.abs(rect.right - width) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.right - width), top: 0, height, left: width, side: 'right', screen: true })
}
}
}
windowsMove(original, rect, horizontal, vertical)
{
for (let win of this.wm.windows)
{
if (!win.options.noSnap && win !== original)
{
const rect2 = win.win.getBoundingClientRect()
if (rect.left - this.options.snap <= rect2.right && rect.right + this.options.snap >= rect2.left)
{
if (Math.abs(rect.top - rect2.bottom) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.top - rect2.bottom), left: rect2.left, width: rect2.width, top: rect2.bottom, side: 'top' })
if (Math.abs(rect.left - rect2.left) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.left - rect2.left), top: rect2.top, height: rect2.height, left: rect2.left, side: 'left', noSpacing: true })
}
else if (Math.abs(rect.right - rect2.right) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.right - rect2.right), top: rect2.top, height: rect2.height, left: rect2.right, side: 'right', noSpacing: true })
}
}
else if (Math.abs(rect.bottom - rect2.top) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.bottom - rect2.top), left: rect2.left, width: rect2.width, top: rect2.top, side: 'bottom' })
if (Math.abs(rect.left - rect2.left) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.left - rect2.left), top: rect2.top, height: rect2.height, left: rect2.left, side: 'left', noSpacing: true })
}
else if (Math.abs(rect.right - rect2.right) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.right - rect2.right), top: rect2.top, height: rect2.height, left: rect2.right, side: 'right', noSpacing: true })
}
}
}
if (rect.top - this.options.snap <= rect2.bottom && rect.bottom + this.options.snap >= rect2.top)
{
if (Math.abs(rect.left - rect2.right) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.left - rect2.right), top: rect2.top, height: rect2.height, left: rect2.right, side: 'left' })
if (Math.abs(rect.top - rect2.top) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.top - rect2.top), left: rect2.left, width: rect2.width, top: rect2.top, side: 'top', noSpacing: true })
}
else if (Math.abs(rect.bottom - rect2.bottom) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.bottom - rect2.bottom), left: rect2.left, width: rect2.width, top: rect2.bottom, side: 'bottom', noSpacing: true })
}
}
else if (Math.abs(rect.right - rect2.left) <= this.options.snap)
{
vertical.push({ distance: Math.abs(rect.right - rect2.left), top: rect2.top, height: rect2.height, left: rect2.left, side: 'right' })
if (Math.abs(rect.top - rect2.top) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.top - rect2.top), left: rect2.left, width: rect2.width, top: rect2.top, side: 'top', noSpacing: true })
}
else if (Math.abs(rect.bottom - rect2.bottom) <= this.options.snap)
{
horizontal.push({ distance: Math.abs(rect.bottom - rect2.bottom), left: rect2.left, width: rect2.width, top: rect2.bottom, side: 'bottom', noSpacing: true })
}
}
}
}
}
}
move(win)
{
if (this.stopped || win.options.noSnap || win.isModal())
{
return
}
this.horizontal.style.display = 'none'
this.vertical.style.display = 'none'
const horizontal = []
const vertical = []
const rect = win.win.getBoundingClientRect()
if (this.options.screen)
{
this.screenMove(rect, horizontal, vertical)
}
if (this.options.windows)
{
this.windowsMove(win, rect, horizontal, vertical)
}
if (horizontal.length)
{
horizontal.sort((a, b) => { return a.distance - b.distance })
const find = horizontal[0]
this.horizontal.style.display = 'block'
this.horizontal.style.width = find.width + 'px'
this.horizontal.y = find.top - this.options.indicator / 2
this.horizontal.style.transform = `translate(${find.left}px,${this.horizontal.y}px)`
this.horizontal.side = find.side
this.horizontal.noSpacing = find.noSpacing
this.horizontal.screen = find.screen
}
if (vertical.length)
{
vertical.sort((a, b) => { return a.distance - b.distance })
const find = vertical[0]
this.vertical.style.display = 'block'
this.vertical.style.height = find.height + 'px'
this.vertical.x = find.left - this.options.indicator / 2
this.vertical.style.transform = `translate(${this.vertical.x}px,${find.top}px)`
this.vertical.side = find.side
this.vertical.noSpacing = find.noSpacing
this.vertical.screen = find.screen
}
}
moveEnd(win)
{
if (this.stopped)
{
return
}
if (this.horizontal.style.display === 'block')
{
const spacing = this.horizontal.noSpacing ? 0 : this.options.spacing
const adjust = win.minimized ? (win.height - win.height * win.minimized.scaleY) / 2 : 0
switch (this.horizontal.side)
{
case 'top':
win.y = this.horizontal.y - adjust + spacing + this.options.indicator / 2
break
case 'bottom':
win.bottom = Math.floor(this.horizontal.y + adjust - spacing + this.options.indicator / 2)
break
}
win.attachToScreen('vertical', this.horizontal.screen ? this.horizontal.side : '')
}
if (this.vertical.style.display === 'block')
{
const spacing = this.vertical.noSpacing ? 0 : this.options.spacing
const adjust = win.minimized ? (win.width - win.width * win.minimized.scaleX) / 2 : 0
switch (this.vertical.side)
{
case 'left':
win.x = this.vertical.x - adjust + spacing + this.options.indicator / 2
break
case 'right':
win.right = Math.floor(this.vertical.x + adjust - spacing + this.options.indicator / 2)
break
}
win.attachToScreen('horziontal', this.vertical.screen ? this.vertical.side : '')
}
this.horizontal.style.display = this.vertical.style.display = 'none'
}
}