www.spaceplanner.app

Web client to the spaceplanner API
git clone git://jacobedwards.org/www.spaceplanner.app
Log | Files | Refs

commit 4121779d42ce443ffa8994bce42505428f5a5f46
parent be9932e5679f6e9f285a121b03a2c30db6097f50
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Tue,  8 Oct 2024 15:42:52 -0700

Add hidden margins to aid in selecting objects

Now objects are selected if they're origin is X units from the
pointer, even if they're not that big. This is primarily for touch
devices where it can be difficult to select objects accurately.

Diffstat:
Mfiles/floorplans/floorplan/editor.js | 34++++++++++++++++++++++++++++------
Mfiles/floorplans/floorplan/geometry.js | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mfiles/floorplans/floorplan/main.js | 30++++++++++++++++++------------
3 files changed, 114 insertions(+), 21 deletions(-)

diff --git a/files/floorplans/floorplan/editor.js b/files/floorplans/floorplan/editor.js @@ -121,6 +121,21 @@ SVG.extend(SVG.Element, { throw new Error("Didn't find " + selector) } return r + }, + + touching: function(x, y, minsize) { + let b = this.bbox() + let d = minsize - b.width + if (d > 0) { + b.x -= d / 2 + b.width = minsize + } + d = minsize - b.height + if (d > 0) { + b.y -= d / 2 + b.height = minsize + } + return (x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height) } }) @@ -534,23 +549,30 @@ export class FloorplanEditor { return this.thingAt(point, "#points") } - thingAt(point, selector) { - return this.thingsAt(point, selector, 1)[0] + thingAt(point, selector, options) { + options = options ?? {} + options.max = 1 + return this.thingsAt(point, selector, options)[0] } - thingsAt(point, selector, max) { + thingsAt(point, selector, options) { + options = options ?? {} + let children = this.draw.find(selector ?? "*") .children() .toArray() + let done = {} let inside = [] - for (let i in children) { - if (children[i].inside(point.x, point.y)) { - if (inside.push(children[i]) >= max) { + for (let i = 0; i < children.length; ++i) { + if (children[i][options.method ?? "inside"](point.x, point.y, options.minsize)) { + if (inside.push(children[i]) >= options.max) { return inside } + children[i] = null } } + return inside } diff --git a/files/floorplans/floorplan/geometry.js b/files/floorplans/floorplan/geometry.js @@ -16,6 +16,24 @@ SVG.extend(SVG.Circle, { SVG.extend(SVG.Shape, { vec: function() { return new Vector2(this.x(), this.y()) + }, + + distanceTo: function(x, y) { + return this.bbox().distanceTo(x, y) + }, + + touching: function(x, y, minsize) { + let b = this.bbox() + if (b.width < minsize) { + b.x -= (minsize - b.width) / 2 + b.width = minsize + } + if (b.height < minsize) { + b.y -= (minsize - b.height) / 2 + b.height = minsize + } + return x >= b.x && x <= b.x + b.width && + y >= b.y && y <= b.y + b.height } }) @@ -81,9 +99,11 @@ SVG.extend(SVG.Line, { } }, - whereIsPoint: function(x, y) { + whereIsPoint: function(x, y, width) { let p = new Vector2(x, y) - let width = this.attr("stroke-width") ?? 1 + if (width == null) { + width = this.attr("stroke-width") ?? 1 + } let closest = this.closestPoint(p) /* @@ -102,6 +122,51 @@ SVG.extend(SVG.Line, { // This must use x and y to be compatible with Shape's inside() inside: function(x, y) { - return this.whereIsPoint(x, y) != null ? true : false + return this.whereIsPoint(x, y) != null + }, + + touching: function(x, y, width) { + return this.whereIsPoint(x, y, width) != null + }, + + closestEdge: function(x, y) { + let p = new Vector2(x, y) + let w = this.attr("stroke-width") ?? 1 + let c = this.closestPoint(p) + + let b = new SVG.Box(c.x - w / 2, c.y - w / 2, w, w) + return b.closestEdge(p.x, p.y) + }, + + distanceTo: function(x, y) { + return this.closestEdge(x, y).distanceTo(new Vector2(x, y)) + } +}) + +SVG.extend(SVG.Box, { + closestEdge: function(x, y) { + let ex + if (x < this.x) { + ex = this.x + } else if (x > this.x + this.width) { + ex = this.x + this.width + } else { + ex = x + } + let ey + if (y < this.y) { + ey = this.y + } else if (y > this.y + this.height) { + ey = this.y + this.height + } else { + ey = y + } + + let ev = new Vector2(ex, ey) + return ev + }, + + distanceTo: function(x, y) { + return this.closestEdge(x, y).distanceTo(new Vector2(x, y)) } }) diff --git a/files/floorplans/floorplan/main.js b/files/floorplans/floorplan/main.js @@ -345,22 +345,28 @@ function selectionHandler(event, editor) { } let p = editor.draw.point(event.clientX, event.clientY) - - let x = editor.thingAt(p, "#" + editor.layoutG()) - if (x) { - x.select() - return + let order = [ "#" + editor.layoutG(), "#points", "#pointmaps" ] + for (let i = 0; i < order.length; ++i) { + let x = editor.thingAt(p, order[i]) + if (x) { + x.select() + return + } } - x = editor.thingAt(p, "#points") - if (x) { - x.select() - return + let close = editor.thingsAt(p, order.join(","), { method: "touching", minsize: 3500 }) + let dist + let closest + for (let i = 0; i < close.length; ++i) { + let tmp = close[i].distanceTo(p.x, p.y) + if (dist == null || tmp < dist) { + dist = tmp + closest = close[i] + } } - x = editor.thingAt(p, "#pointmaps") - if (x) { - x.select() + if (closest != null) { + closest.select() return }