tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
import {
|
|
|
|
GapsSnapLine,
|
|
|
|
PointsSnapLine,
|
|
|
|
SnapLine,
|
|
|
|
TLArrowShape,
|
2023-12-19 11:47:40 +00:00
|
|
|
TLGeoShape,
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
TLShapeId,
|
|
|
|
TLShapePartial,
|
2024-01-03 12:13:15 +00:00
|
|
|
Vec,
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
createShapeId,
|
|
|
|
} from '@tldraw/editor'
|
|
|
|
import { TestEditor } from './TestEditor'
|
|
|
|
import { getSnapLines } from './getSnapLines'
|
2023-06-12 14:04:14 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
let editor: TestEditor
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
afterEach(() => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor?.dispose()
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
const ids = {
|
2023-06-03 20:46:53 +00:00
|
|
|
frame1: createShapeId('frame1'),
|
|
|
|
frame2: createShapeId('frame2'),
|
|
|
|
box1: createShapeId('box1'),
|
|
|
|
box2: createShapeId('box2'),
|
|
|
|
line1: createShapeId('line1'),
|
|
|
|
boxD: createShapeId('boxD'),
|
|
|
|
boxE: createShapeId('boxE'),
|
|
|
|
boxF: createShapeId('boxF'),
|
|
|
|
boxG: createShapeId('boxG'),
|
|
|
|
boxH: createShapeId('boxH'),
|
|
|
|
boxX: createShapeId('boxX'),
|
|
|
|
|
|
|
|
boxT: createShapeId('boxT'),
|
|
|
|
|
|
|
|
lineA: createShapeId('lineA'),
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
console.error = jest.fn()
|
2023-06-02 15:21:45 +00:00
|
|
|
editor = new TestEditor()
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
const getNumSnapPoints = (snap: SnapLine): number => {
|
|
|
|
return snap.type === 'points' ? snap.points.length : (null as any as number)
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertGaps(snap: SnapLine): asserts snap is GapsSnapLine {
|
|
|
|
expect(snap.type).toBe('gaps')
|
|
|
|
}
|
|
|
|
|
|
|
|
function getGapAndPointLines(snaps: SnapLine[]) {
|
|
|
|
const gapLines = snaps.filter((snap) => snap.type === 'gaps') as GapsSnapLine[]
|
|
|
|
const pointLines = snaps.filter((snap) => snap.type === 'points') as PointsSnapLine[]
|
|
|
|
return { gapLines, pointLines }
|
|
|
|
}
|
|
|
|
|
|
|
|
const box = (id: TLShapeId, x: number, y: number, w = 10, h = 10): TLShapePartial => ({
|
|
|
|
type: 'geo',
|
|
|
|
id,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
props: {
|
|
|
|
w,
|
|
|
|
h,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('When translating...', () => {
|
|
|
|
beforeEach(() => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 10,
|
|
|
|
y: 10,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box2,
|
|
|
|
type: 'geo',
|
|
|
|
x: 200,
|
|
|
|
y: 200,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.line1,
|
|
|
|
type: 'line',
|
|
|
|
x: 100,
|
|
|
|
y: 100,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
|
|
|
it('enters and exits the translating state', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.pointerDown(50, 50, ids.box1)
|
|
|
|
.expectToBeIn('select.pointing_shape')
|
|
|
|
.pointerMove(50, 40)
|
|
|
|
.expectToBeIn('select.translating')
|
|
|
|
.pointerUp()
|
|
|
|
.expectToBeIn('select.idle')
|
|
|
|
})
|
|
|
|
|
|
|
|
it('exits the translating state when canceled', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.pointerDown(50, 50, ids.box1)
|
|
|
|
.pointerMove(50, 40) // [0, -10]
|
|
|
|
.expectToBeIn('select.translating')
|
|
|
|
.cancel()
|
|
|
|
.expectToBeIn('select.idle')
|
|
|
|
})
|
|
|
|
|
|
|
|
it('translates a single shape', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-08-01 17:03:31 +00:00
|
|
|
.pointerDown(50, 50, ids.box1)
|
2023-04-25 11:01:25 +00:00
|
|
|
.pointerMove(50, 40) // [0, -10]
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 10, y: 0 })
|
|
|
|
.pointerMove(100, 100) // [50, 50]
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 60, y: 60 })
|
|
|
|
.pointerUp()
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 60, y: 60 })
|
|
|
|
})
|
|
|
|
|
2023-12-15 23:37:03 +00:00
|
|
|
it('translates a single shape near the top left edge', () => {
|
|
|
|
editor.user.updateUserPreferences({ edgeScrollSpeed: 1 })
|
|
|
|
editor.pointerDown(50, 50, ids.box1).pointerMove(0, 50) // [-50, 0]
|
|
|
|
|
2023-12-19 11:47:40 +00:00
|
|
|
const before = editor.getShape<TLGeoShape>(ids.box1)!
|
|
|
|
|
2023-12-15 23:37:03 +00:00
|
|
|
jest.advanceTimersByTime(100)
|
|
|
|
editor
|
|
|
|
// The change is bigger than expected because the camera moves
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: -160, y: 10 })
|
|
|
|
// We'll continue moving in the x postion, but now we'll also move in the y position.
|
|
|
|
// The speed in the y position is smaller since we are further away from the edge.
|
|
|
|
.pointerMove(0, 25)
|
|
|
|
jest.advanceTimersByTime(100)
|
2023-12-19 11:47:40 +00:00
|
|
|
editor.pointerUp()
|
|
|
|
|
|
|
|
const after = editor.getShape<TLGeoShape>(ids.box1)!
|
|
|
|
|
|
|
|
expect(after.x).toBeLessThan(before.x)
|
|
|
|
expect(after.y).toBeLessThan(before.y)
|
|
|
|
expect(after.props.w).toEqual(before.props.w)
|
|
|
|
expect(after.props.h).toEqual(before.props.h)
|
2023-12-15 23:37:03 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('translates a single shape near the bottom right edge', () => {
|
|
|
|
editor.user.updateUserPreferences({ edgeScrollSpeed: 1 })
|
|
|
|
editor.pointerDown(50, 50, ids.box1).pointerMove(1080, 50)
|
|
|
|
|
|
|
|
jest.advanceTimersByTime(100)
|
|
|
|
editor
|
|
|
|
// The change is bigger than expected because the camera moves
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 1140, y: 10 })
|
|
|
|
.pointerMove(1080, 800)
|
|
|
|
jest.advanceTimersByTime(100)
|
|
|
|
editor
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 1280, y: 845.68 })
|
|
|
|
.pointerUp()
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 1280, y: 845.68 })
|
|
|
|
})
|
|
|
|
|
2023-04-25 11:01:25 +00:00
|
|
|
it('translates multiple shapes', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.select(ids.box1, ids.box2)
|
|
|
|
.pointerDown(50, 50, ids.box1)
|
|
|
|
.pointerMove(50, 40) // [0, -10]
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 10, y: 0 }, { id: ids.box2, x: 200, y: 190 })
|
|
|
|
.pointerMove(100, 100) // [50, 50]
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 60, y: 60 }, { id: ids.box2, x: 250, y: 250 })
|
|
|
|
.pointerUp()
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 60, y: 60 }, { id: ids.box2, x: 250, y: 250 })
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('When cloning...', () => {
|
|
|
|
beforeEach(() => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 10,
|
|
|
|
y: 10,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box2,
|
|
|
|
type: 'geo',
|
|
|
|
x: 200,
|
|
|
|
y: 200,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.line1,
|
|
|
|
type: 'line',
|
|
|
|
x: 100,
|
|
|
|
y: 100,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
})
|
2023-08-15 16:25:50 +00:00
|
|
|
|
2023-04-25 11:01:25 +00:00
|
|
|
it('clones a single shape and restores when stopping cloning', () => {
|
2023-12-15 23:37:03 +00:00
|
|
|
// Move the camera so that we are not at the edges, which causes the camera to move when we translate
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(3)
|
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(3)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1).pointerDown(50, 50, ids.box1).pointerMove(50, 40) // [0, -10]
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(3)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.expectShapeToMatch({ id: ids.box1, x: 10, y: 0 }) // Translated A...
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Start cloning!
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Alt')
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(4)
|
2023-11-13 14:31:27 +00:00
|
|
|
const newShape = editor.getSelectedShapes()[0]
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(newShape.id).not.toBe(ids.box1)
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 10, y: 10 }) // A should be back to original position...
|
|
|
|
.expectShapeToMatch({ id: newShape.id, x: 10, y: 0 }) // New node should be at A's previous position
|
|
|
|
.pointerMove(60, 40) // [10, -10]
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 10, y: 10 }) // No movement on A
|
|
|
|
.expectShapeToMatch({ id: newShape.id, x: 20, y: 0 }) // Clone should be moving
|
|
|
|
|
|
|
|
// Stop cloning!
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyUp('Alt')
|
2023-04-25 11:01:25 +00:00
|
|
|
jest.advanceTimersByTime(500)
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.expectShapeToMatch({ id: ids.box1, x: 20, y: 0 }) // A should be at the translated position...
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(newShape.id)).toBeUndefined() // And the new node should be gone!
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('clones multiple single shape and restores when stopping cloning', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1, ids.box2).pointerDown(50, 50, ids.box1).pointerMove(50, 40) // [0, -10]
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(3)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.expectShapeToMatch({ id: ids.box1, x: 10, y: 0 }) // Translated A...
|
|
|
|
editor.expectShapeToMatch({ id: ids.box2, x: 200, y: 190 }) // Translated B...
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Start cloning!
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Alt')
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(5) // Two new shapes!
|
2023-11-13 12:42:07 +00:00
|
|
|
const newShapeA = editor.getShape(editor.getSelectedShapeIds()[0])!
|
|
|
|
const newShapeB = editor.getShape(editor.getSelectedShapeIds()[1])!
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(newShapeA).toBeDefined()
|
|
|
|
expect(newShapeB).toBeDefined()
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 10, y: 10 }) // A should be back to original position...
|
|
|
|
.expectShapeToMatch({ id: ids.box2, x: 200, y: 200 }) // B should be back to original position...
|
|
|
|
.expectShapeToMatch({ id: newShapeA.id, x: 10, y: 0 }) // New node should be at A's previous position
|
|
|
|
.expectShapeToMatch({ id: newShapeB.id, x: 200, y: 190 }) // New node should be at B's previous position
|
|
|
|
.pointerMove(60, 40) // [10, -10]
|
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 10, y: 10 }) // No movement on A
|
|
|
|
.expectShapeToMatch({ id: ids.box2, x: 200, y: 200 }) // No movement on B
|
|
|
|
.expectShapeToMatch({ id: newShapeA.id, x: 20, y: 0 }) // Clone A should be moving
|
|
|
|
.expectShapeToMatch({ id: newShapeB.id, x: 210, y: 190 }) // Clone B should be moving
|
|
|
|
|
|
|
|
// Stop cloning!
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyUp('Alt')
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// wait 500ms
|
|
|
|
jest.advanceTimersByTime(500)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.expectShapeToMatch({ id: ids.box1, x: 20, y: 0 }) // A should be at the translated position...
|
|
|
|
.expectShapeToMatch({ id: ids.box2, x: 210, y: 190 }) // B should be at the translated position...
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(newShapeA.id)).toBeUndefined() // And the new node A should be gone!
|
|
|
|
expect(editor.getShape(newShapeB.id)).toBeUndefined() // And the new node B should be gone!
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('clones a parent and its descendants and removes descendants when stopping cloning', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([{ id: ids.line1, type: 'geo', parentId: ids.box2 }])
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)!.parentId).toBe(ids.box2)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box2).pointerDown(250, 250, ids.box2).pointerMove(250, 240) // [0, -10]
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(3)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Alt', { altKey: true })
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(5) // Creates a clone of B and C (its descendant)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-13 12:42:07 +00:00
|
|
|
const newShapeA = editor.getShape(editor.getSelectedShapeIds()[0])!
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const newShapeB = editor.getShape(editor.getSortedChildIdsForParent(newShapeA.id)[0])!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(newShapeA).toBeDefined()
|
|
|
|
expect(newShapeB).toBeDefined()
|
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const cloneB = newShapeA.x === editor.getShape(ids.box2)!.x ? newShapeA : newShapeB
|
|
|
|
const cloneC = newShapeA.x === editor.getShape(ids.box2)!.x ? newShapeB : newShapeA
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.expectShapeToMatch({ id: ids.box2, x: 200, y: 200 }) // B should be back to original position...
|
|
|
|
.expectShapeToMatch({ id: cloneB.id, x: 200, y: 190 }) // New node should be at A's previous position
|
|
|
|
.expectShapeToMatch({ id: cloneC.id, x: 100, y: 100 }) // New node should be at B's previous position
|
|
|
|
.pointerMove(260, 240) // [10, -10]
|
|
|
|
.expectShapeToMatch({ id: ids.box2, x: 200, y: 200 }) // No movement on B
|
|
|
|
.expectShapeToMatch({ id: cloneB.id, x: 210, y: 190 }) // Clone A should be moving
|
|
|
|
.expectShapeToMatch({ id: cloneC.id, x: 100, y: 100 }) // New node should be at B's previous position
|
|
|
|
|
|
|
|
// Stop cloning!
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyUp('Alt')
|
2023-04-25 11:01:25 +00:00
|
|
|
// wait 500ms
|
|
|
|
jest.advanceTimersByTime(500)
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.expectShapeToMatch({ id: ids.box2, x: 210, y: 190 }) // B should be at the translated position...
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(cloneB.id)).toBeUndefined() // And the new node A should be gone!
|
|
|
|
expect(editor.getShape(cloneC.id)).toBeUndefined() // And the new node B should be gone!
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('Clones twice', () => {
|
2023-06-03 20:46:53 +00:00
|
|
|
const groupId = createShapeId('g')
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.groupShapes([ids.box1, ids.box2], groupId)
|
2023-11-14 16:32:27 +00:00
|
|
|
const count1 = editor.getCurrentPageShapes().length
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
editor.pointerDown(50, 50, { shape: editor.getShape(groupId)!, target: 'shape' })
|
2023-11-14 13:02:50 +00:00
|
|
|
editor.expectToBeIn('select.pointing_shape')
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(199, 199)
|
2023-11-14 13:02:50 +00:00
|
|
|
editor.expectToBeIn('select.translating')
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getCurrentPageShapes().length).toBe(count1) // 2 new box and group
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Alt')
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-14 13:02:50 +00:00
|
|
|
editor.expectToBeIn('select.translating')
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getCurrentPageShapes().length).toBe(count1 + 3) // 2 new box and group
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyUp('Alt')
|
2023-04-25 11:01:25 +00:00
|
|
|
jest.advanceTimersByTime(500)
|
|
|
|
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getCurrentPageShapes().length).toBe(count1) // 2 new box and group
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Alt')
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getCurrentPageShapes().length).toBe(count1 + 3) // 2 new box and group
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('When translating shapes that are descendants of a rotated shape...', () => {
|
|
|
|
beforeEach(() => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 10,
|
|
|
|
y: 10,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box2,
|
|
|
|
type: 'geo',
|
|
|
|
x: 200,
|
|
|
|
y: 200,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.line1,
|
|
|
|
type: 'line',
|
|
|
|
x: 100,
|
|
|
|
y: 100,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Translates correctly', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.boxD,
|
|
|
|
parentId: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 20,
|
|
|
|
y: 20,
|
|
|
|
props: {
|
|
|
|
w: 10,
|
|
|
|
h: 10,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
const shapeA = editor.getShape(ids.box1)!
|
|
|
|
const shapeD = editor.getShape(ids.boxD)!
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2024-01-03 12:13:15 +00:00
|
|
|
expect(editor.getPageCenter(shapeA)).toMatchObject(new Vec(60, 60))
|
|
|
|
expect(editor.getShapeGeometry(shapeD).center).toMatchObject(new Vec(5, 5))
|
|
|
|
expect(editor.getPageCenter(shapeD)).toMatchObject(new Vec(35, 35))
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
const rads = 0
|
|
|
|
|
2024-01-03 12:13:15 +00:00
|
|
|
expect(editor.getPageCenter(shapeA)).toMatchObject(new Vec(60, 60))
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Expect the node's page position to be rotated around its parent's page center
|
2023-06-02 15:21:45 +00:00
|
|
|
expect(editor.getPageCenter(shapeD)).toMatchObject(
|
2024-01-03 12:13:15 +00:00
|
|
|
new Vec(35, 35).rotWith(editor.getPageCenter(shapeA)!, rads)
|
2023-04-25 11:01:25 +00:00
|
|
|
)
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
const centerD = editor.getPageCenter(shapeD)!.clone().toFixed()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.select(ids.boxD)
|
|
|
|
.pointerDown(centerD.x, centerD.y, ids.boxD)
|
|
|
|
.pointerMove(centerD.x, centerD.y - 10)
|
|
|
|
.pointerMove(centerD.x, centerD.y - 10)
|
|
|
|
.pointerUp()
|
|
|
|
|
2024-01-03 12:13:15 +00:00
|
|
|
expect(editor.getPageCenter(shapeD)).toMatchObject(new Vec(centerD.x, centerD.y - 10))
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
const centerA = editor.getPageCenter(shapeA)!.clone().toFixed()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.select(ids.box1)
|
|
|
|
.pointerDown(centerA.x, centerA.y, ids.box1)
|
|
|
|
.pointerMove(centerA.x, centerA.y - 100)
|
|
|
|
.pointerUp()
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
const centerB = editor.getPageCenter(shapeA)!.clone().toFixed()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(centerB).toMatchObject({ x: centerA.x, y: centerA.y - 100 })
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('snapping with single shapes', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
// 0 10 20 30
|
|
|
|
// ┌──────┐ ┌──────┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └──────┘ └──────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
props: { w: 10, h: 10 },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box2,
|
|
|
|
type: 'geo',
|
|
|
|
x: 20,
|
|
|
|
y: 0,
|
|
|
|
props: { w: 10, h: 10 },
|
|
|
|
},
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
|
|
|
it('happens when the ctrl key is pressed', () => {
|
|
|
|
// 0 10 11 21
|
|
|
|
// ┌──────┐ ┌──────┐
|
|
|
|
// │ │ │ │ <- dragging left
|
|
|
|
// └──────┘ └──────┘
|
|
|
|
//
|
|
|
|
// │
|
|
|
|
// │ press ctrl
|
|
|
|
// ▼
|
|
|
|
//
|
|
|
|
// 0 10 20
|
|
|
|
// ┌──────┬──────┐
|
|
|
|
// │ │ │ *snap*
|
|
|
|
// └──────┴──────┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(16, 5)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// expect box B to be at 11, 0
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 11, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// press ctrl key and it snaps to 10, 0
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Control')
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 10, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// release ctrl key and it unsnaps
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyUp('Control')
|
2023-04-25 11:01:25 +00:00
|
|
|
jest.advanceTimersByTime(200)
|
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 11, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// press ctrl and release the pointer and it should stay snapped
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Control')
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 10, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerUp(16, 5, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 10, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('snaps to the center point as well as all four corners of a bounding box', () => {
|
|
|
|
// ┌──────┐
|
|
|
|
// │ B │
|
|
|
|
// └──────┘
|
|
|
|
// ┌──────┐
|
|
|
|
// │ A │
|
|
|
|
// └──────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(-6, -6, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: -10, y: -10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// ┌──────┐
|
|
|
|
// │ B │
|
|
|
|
// └──────┘
|
|
|
|
// ┌──────┐
|
|
|
|
// │ A │
|
|
|
|
// └──────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(16, -6, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 10, y: -10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// ┌──────┐
|
|
|
|
// │ A │
|
|
|
|
// └──────┘
|
|
|
|
// ┌──────┐
|
|
|
|
// │ B │
|
|
|
|
// └──────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(16, 16, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 10, y: 10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// ┌──────┐
|
|
|
|
// │ A │
|
|
|
|
// └──────┘
|
|
|
|
// ┌──────┐
|
|
|
|
// │ B │
|
|
|
|
// └──────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(-6, 16, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: -10, y: 10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// ┌──────┐
|
|
|
|
// │ AB │
|
|
|
|
// └──────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(6, 6, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)!).toMatchObject({ x: 0, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('creates snap lines + points to render in the UI', () => {
|
|
|
|
// 0 10
|
|
|
|
// ┌──────┐ ┼
|
|
|
|
// │ │
|
|
|
|
// └──────┘ ┼ one line, four points
|
|
|
|
// │
|
|
|
|
// │
|
|
|
|
// │
|
|
|
|
// │11 21
|
|
|
|
// ┼ ┌──────┐
|
|
|
|
// │ │
|
|
|
|
// ┼ └──────┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(16, 35, { ctrlKey: true })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()?.length).toBe(1)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(getNumSnapPoints(editor.snaps.getLines()![0])).toBe(4)
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('shows all the horizonal lines + points where the bounding boxes align', () => {
|
|
|
|
// x─────x────────────────────x─────x
|
|
|
|
// ┌─────┐ ┌─────┐
|
|
|
|
// │ x──┼────────────────────┼──x │
|
|
|
|
// └─────┘ └─────┘
|
|
|
|
// x─────x────────────────────x─────x
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(36, 5, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const snaps = editor.snaps.getLines()!.sort((a, b) => getNumSnapPoints(a) - getNumSnapPoints(b))
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(snaps.length).toBe(3)
|
|
|
|
|
|
|
|
// center snap line
|
|
|
|
expect(getNumSnapPoints(snaps[0])).toBe(2)
|
|
|
|
|
|
|
|
// top and bottom lines
|
|
|
|
expect(getNumSnapPoints(snaps[1])).toBe(4)
|
|
|
|
expect(getNumSnapPoints(snaps[2])).toBe(4)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('shows all the vertical lines + points where the bounding boxes align', () => {
|
|
|
|
// x ┌─────┐ x
|
|
|
|
// │ │ x │ │
|
|
|
|
// x └──┼──┘ x
|
|
|
|
// │ │ │
|
|
|
|
// x ┌──┼──┐ x
|
|
|
|
// │ │ x │ │
|
|
|
|
// x └─────┘ x
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(5, 45, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const snaps = editor.snaps.getLines()!.sort((a, b) => getNumSnapPoints(a) - getNumSnapPoints(b))
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(snaps.length).toBe(3)
|
|
|
|
|
|
|
|
// center snap line
|
|
|
|
expect(getNumSnapPoints(snaps[0])).toBe(2)
|
|
|
|
|
|
|
|
// left and right lines
|
|
|
|
expect(getNumSnapPoints(snaps[1])).toBe(4)
|
|
|
|
expect(getNumSnapPoints(snaps[2])).toBe(4)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('does not snap to shapes that are not visible in the viewport', () => {
|
|
|
|
// move A off screen
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([{ id: ids.box1, type: 'geo', x: -20 }])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(36, 5, { ctrlKey: true })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()!.length).toBe(0)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-14 11:57:43 +00:00
|
|
|
editor.updateShapes([{ id: ids.box1, type: 'geo', x: editor.getViewportScreenBounds().w + 10 }])
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(33, 5, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()!.length).toBe(0)
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([{ id: ids.box1, type: 'geo', y: -20 }])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(5, 5, { ctrlKey: true })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()!.length).toBe(0)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([
|
2023-11-14 11:57:43 +00:00
|
|
|
{ id: ids.box1, type: 'geo', x: 0, y: editor.getViewportScreenBounds().h + 10 },
|
2023-06-02 15:21:45 +00:00
|
|
|
])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(5, 5, { ctrlKey: true })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()!.length).toBe(0)
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('does not snap on the Y axis if the shift key is pressed', () => {
|
|
|
|
// ┌──────┐ ──────►
|
|
|
|
// ┌──────┐ │ B │ drag with shift
|
|
|
|
// │ A │ └──────┘
|
|
|
|
// └──────┘
|
|
|
|
|
|
|
|
// move B up one pixel
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
editor.updateShapes([{ id: ids.box2, type: 'geo', y: editor.getShape(ids.box2)!.y - 1 }])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 5, ids.box2).pointerMove(36, 5, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// should snap without shift key
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 31, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Shift')
|
2023-04-25 11:01:25 +00:00
|
|
|
// should unsnap with shift key
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 31, y: -1 })
|
2023-04-25 11:01:25 +00:00
|
|
|
// and continue not snapping while moving
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(45, 5, { ctrlKey: true, shiftKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 40, y: -1 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// should still snap to things on the X axis
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([{ type: 'geo', id: ids.line1, x: 100, y: 0, props: { w: 10, h: 10 } }])
|
|
|
|
editor.pointerMove(106, 5, { ctrlKey: true, shiftKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 100, y: -1 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('does not snap on the X axis if the shift key is pressed', () => {
|
|
|
|
// ┌──────┐
|
|
|
|
// │ A │
|
|
|
|
// └──────┘
|
|
|
|
//
|
|
|
|
// ┌──────┐ │
|
|
|
|
// │ B │ drag with shift │
|
|
|
|
// └──────┘ ▼
|
|
|
|
|
|
|
|
// move B into place
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.updateShapes([{ id: ids.box2, type: 'geo', x: 1, y: 20 }])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(6, 25, ids.box2).pointerMove(6, 35, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// should snap without shift key
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 0, y: 30 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Shift')
|
2023-04-25 11:01:25 +00:00
|
|
|
// should unsnap with shift key
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 1, y: 30 })
|
2023-04-25 11:01:25 +00:00
|
|
|
// and continue not snapping while moving
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(6, 50, { ctrlKey: true, shiftKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 1, y: 45 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// should still snap to things on the Y axis
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([{ type: 'geo', id: ids.line1, x: 20, y: 100, props: { w: 10, h: 10 } }])
|
|
|
|
editor.pointerMove(6, 106, { ctrlKey: true, shiftKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 1, y: 100 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('snapping with multiple shapes', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
// 0 100 200 300
|
|
|
|
// ┌──────┐ ┌──────┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └──────┘ └──────┘
|
|
|
|
//
|
|
|
|
// ┌────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ C │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// └────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
props: { w: 100, h: 100 },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box2,
|
|
|
|
type: 'geo',
|
|
|
|
x: 200,
|
|
|
|
y: 0,
|
|
|
|
props: { w: 100, h: 100 },
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.line1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 0,
|
|
|
|
y: 200,
|
|
|
|
props: { w: 300, h: 300 },
|
|
|
|
},
|
|
|
|
])
|
|
|
|
})
|
|
|
|
|
|
|
|
it("will not snap to inidivual shape's edges", () => {
|
|
|
|
// 0 100 200 300
|
|
|
|
// ┌──────┐ ┌──────┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └──────┘ └──────┘
|
|
|
|
//
|
|
|
|
// ┌────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ C │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// └────────────────────┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1, ids.box2)
|
|
|
|
editor.pointerDown(50, 50, ids.box1).pointerMove(249, 50, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)!).toMatchObject({ x: 199, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it("will snap to the selection's bounding box", () => {
|
|
|
|
// 0 100 200 300
|
|
|
|
// ┌──────┐ ┌──────┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └──────┘ └──────┘
|
|
|
|
// ┌────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ C │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// └────────────────────┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1, ids.box2)
|
|
|
|
editor.pointerDown(50, 50, ids.box1).pointerMove(349, 50, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)!).toMatchObject({ x: 300, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('Snap-between behavior', () => {
|
|
|
|
beforeEach(() => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor?.dispose()
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
it('snaps a shape horizontally between two others', () => {
|
|
|
|
// ┌─────┐ ┌─────┐
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ A │ │ B │
|
|
|
|
// │ │ ┌───┐ │ │
|
|
|
|
// │ ├──┼──┤ C ├──┼──┤ │
|
|
|
|
// │ │ └───┘ │ │
|
|
|
|
// └─────┘ └─────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 0, y: 0, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 200, y: 0, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 0, props: { w: 10, h: 10 } },
|
|
|
|
])
|
|
|
|
|
|
|
|
// the midpoint is 125 and c is 10 wide so it should snap to 120 if we put it at 121
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(55, 5, ids.line1).pointerMove(126, 67, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 120, y: 62 })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()?.length).toBe(1)
|
|
|
|
const line = editor.snaps.getLines()![0]
|
|
|
|
assertGaps(line)
|
|
|
|
expect(line.gaps.length).toBe(2)
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
it('shows horizontal point snaps at the same time as horizontal gap snaps', () => {
|
|
|
|
// ┌─────┐ ┌─────┐
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ A │ │ B │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ ┌───┐ │ │
|
|
|
|
// │ ├──┼──┤ C ├──┼──┤ │
|
|
|
|
// └─────┘ └───┘ └─────┘
|
|
|
|
// x─────x─────x───x─────x─────x
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 0, y: 0, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 200, y: 0, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 0, props: { w: 10, h: 10 } },
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(55, 5, ids.line1).pointerMove(126, 94, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 120, y: 90 })
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(pointLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps.length).toBe(2)
|
|
|
|
expect(pointLines[0].points.length).toBe(6)
|
|
|
|
})
|
|
|
|
it('shows vertical point snaps at the same time as horizontal gap snaps', () => {
|
|
|
|
// ┌─────┐ ┌─────┐
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ A │ │ B │
|
|
|
|
// │ │ ┌───┐ │ │
|
|
|
|
// │ ├──┼──┤ C ├──┼──┤ │ x
|
|
|
|
// │ │ └───┘ │ │ │
|
|
|
|
// └─────┘ └─────┘ │
|
|
|
|
// │
|
|
|
|
// ┌───────┐ │
|
|
|
|
// │ D │ x
|
|
|
|
// └───────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 0, y: 0, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 200, y: 0, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 0, props: { w: 10, h: 10 } },
|
|
|
|
{ type: 'geo', id: ids.boxD, x: 75, y: 150, props: { w: 100, h: 10 } },
|
|
|
|
])
|
|
|
|
|
|
|
|
// the midpoint is 125 and c is 10 wide so it should snap to 120 if we put it at 121
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(55, 5, ids.line1).pointerMove(126, 67, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 120, y: 62 })
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(pointLines).toHaveLength(1)
|
|
|
|
|
|
|
|
expect(gapLines[0].gaps.length).toBe(2)
|
|
|
|
expect(pointLines[0].points.length).toBe(2)
|
|
|
|
})
|
|
|
|
it('snaps a shape vertically between two others', () => {
|
|
|
|
// ┌──────────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ A │
|
|
|
|
// │ │
|
|
|
|
// └─────┬────────────────────┘
|
|
|
|
// │
|
|
|
|
// ─┼─
|
|
|
|
// │
|
|
|
|
// ┌─┴─┐
|
|
|
|
// │ C │
|
|
|
|
// └─┬─┘
|
|
|
|
// │
|
|
|
|
// ─┼─
|
|
|
|
// │
|
|
|
|
// ┌─────┴────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ B │
|
|
|
|
// │ │
|
|
|
|
// └──────────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 0, y: 0, props: { w: 100, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 0, y: 200, props: { w: 100, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 150, props: { w: 10, h: 10 } },
|
|
|
|
])
|
|
|
|
// the midpoint is 125 and c is 10 wide so it should snap to 120 if we put it at 121
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(55, 155, ids.line1).pointerMove(27, 126, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 22, y: 120 })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()?.length).toBe(1)
|
|
|
|
assertGaps(editor.snaps.getLines()![0])
|
|
|
|
const { gapLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines[0].gaps.length).toBe(2)
|
|
|
|
})
|
|
|
|
it('shows vertical snap points at the same time as vertical gaps', () => {
|
|
|
|
// x ┌──────────────────────────┐
|
|
|
|
// │ │ │
|
|
|
|
// │ │ A │
|
|
|
|
// │ │ │
|
|
|
|
// x └─┬────────────────────────┘
|
|
|
|
// │ │
|
|
|
|
// │ ─┼─
|
|
|
|
// │ │
|
|
|
|
// x ┌─┴─┐
|
|
|
|
// │ │ C │
|
|
|
|
// x └─┬─┘
|
|
|
|
// │ │
|
|
|
|
// │ ─┼─
|
|
|
|
// │ │
|
|
|
|
// x ┌─┴────────────────────────┐
|
|
|
|
// │ │ │
|
|
|
|
// │ │ B │
|
|
|
|
// │ │ │
|
|
|
|
// x └──────────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 0, y: 0, props: { w: 100, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 0, y: 200, props: { w: 100, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 150, props: { w: 10, h: 10 } },
|
|
|
|
])
|
|
|
|
// the midpoint is 125 and c is 10 wide so it should snap to 120 if we put it at 121
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(55, 155, ids.line1).pointerMove(6, 126, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 0, y: 120 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(pointLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps.length).toBe(2)
|
|
|
|
expect(pointLines[0].points.length).toBe(6)
|
|
|
|
})
|
|
|
|
it('shows horizontal snap points at the same time as vertical gaps', () => {
|
|
|
|
// ┌──────────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ A │
|
|
|
|
// │ │
|
|
|
|
// └────┬─────────────────────┘
|
|
|
|
// │
|
|
|
|
// ─┼─ D┌───────────┐
|
|
|
|
// │ │ │
|
|
|
|
// C┌─┴─┐ │ │
|
|
|
|
// │ x─┼───────┼─────x │
|
|
|
|
// └─┬─┘ │ │
|
|
|
|
// │ │ │
|
|
|
|
// ─┼─ └───────────┘
|
|
|
|
// │
|
|
|
|
// ┌────┴─────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ B │
|
|
|
|
// │ │
|
|
|
|
// └──────────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 0, y: 0, props: { w: 100, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 0, y: 200, props: { w: 100, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 150, props: { w: 10, h: 10 } },
|
|
|
|
{ type: 'geo', id: ids.boxD, x: 50, y: 75, props: { w: 10, h: 100 } },
|
|
|
|
])
|
|
|
|
// the midpoint is 125 and c is 10 wide so it should snap to 120 if we put it at 121
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(55, 155, ids.line1).pointerMove(27, 126, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 22, y: 120 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(pointLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
expect(pointLines[0].points).toHaveLength(2)
|
|
|
|
})
|
|
|
|
it('can happen on multiple axes at the same time', () => {
|
|
|
|
// ┌──────────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ A │
|
|
|
|
// ┌─────┐ │ ┌─────┐ │
|
|
|
|
// │ │ └─────┬─────────┼─────┼────┘
|
|
|
|
// │ │ │ │ │
|
|
|
|
// │ │ ─┼─ │ │
|
|
|
|
// │ D │ │ │ B │
|
|
|
|
// │ │ ┌─┴─┐ │ │
|
|
|
|
// │ ├───┼───┤ E ├───┼───┤ │
|
|
|
|
// │ │ └─┬─┘ │ │
|
|
|
|
// └─────┘ │ └─────┘
|
|
|
|
// ─┼─
|
|
|
|
// │
|
|
|
|
// ┌─────┴────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// │ C │
|
|
|
|
// │ │
|
|
|
|
// └──────────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ type: 'geo', id: ids.box1, x: 50, y: 0, props: { w: 200, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.box2, x: 150, y: 50, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.line1, x: 50, y: 200, props: { w: 200, h: 50 } },
|
|
|
|
{ type: 'geo', id: ids.boxD, x: 0, y: 50, props: { w: 50, h: 100 } },
|
|
|
|
{ type: 'geo', id: ids.boxE, x: 0, y: 0, props: { w: 10, h: 10 } },
|
|
|
|
])
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 5, ids.boxE).pointerMove(101, 126, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxE)).toMatchObject({ x: 95, y: 120 })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()?.length).toBe(2)
|
|
|
|
assertGaps(editor.snaps.getLines()![0])
|
|
|
|
assertGaps(editor.snaps.getLines()![1])
|
|
|
|
const { gapLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines[0].gaps.length).toBe(2)
|
|
|
|
expect(gapLines[1].gaps.length).toBe(2)
|
|
|
|
})
|
|
|
|
it('will expand a horizontal and vertical selections outwards if possible', () => {
|
|
|
|
// ┌───┐
|
|
|
|
// │ E │
|
|
|
|
// └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌─┴─┐
|
|
|
|
// │ F │
|
|
|
|
// └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐
|
|
|
|
// │ A ├─┼─┤ B ├─┼─┤ X ├─┼─┤ C ├─┼─┤ D │
|
|
|
|
// └───┘ └───┘ └─┬─┘ └───┘ └───┘
|
|
|
|
// ┼
|
|
|
|
// ┌─┴─┐
|
|
|
|
// │ G │
|
|
|
|
// └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌─┴─┐
|
|
|
|
// │ H │
|
|
|
|
// └───┘
|
|
|
|
// dragging X
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 0, 40),
|
|
|
|
box(ids.box2, 20, 40),
|
|
|
|
box(ids.line1, 60, 40),
|
|
|
|
box(ids.boxD, 80, 40),
|
|
|
|
box(ids.boxE, 40, 0),
|
|
|
|
box(ids.boxF, 40, 20),
|
|
|
|
box(ids.boxG, 40, 60),
|
|
|
|
box(ids.boxH, 40, 80),
|
|
|
|
|
|
|
|
box(ids.boxX, 0, 0),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 5, ids.boxX).pointerMove(46, 46, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 40, y: 40 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines).toHaveLength(2)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(4)
|
|
|
|
expect(gapLines[1].gaps).toHaveLength(4)
|
|
|
|
|
|
|
|
// it should also have snap lines for all the edge/center alignments
|
|
|
|
expect(pointLines).toHaveLength(6)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('will show multiple non-overlapping snap-betweens on the same axis', () => {
|
|
|
|
// ┌─────┐ ┌─────┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └──┬──┘ └──┬──┘
|
|
|
|
// ┼ ┼
|
|
|
|
// ┌──┴─────────┴──┐
|
|
|
|
// │ X drag │
|
|
|
|
// └──┬─────────┬──┘
|
|
|
|
// ┼ ┼
|
|
|
|
// ┌──┴──┐ ┌──┴──┐
|
|
|
|
// │ C │ │ D │
|
|
|
|
// └─────┘ └─────┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 0, 0),
|
|
|
|
box(ids.box2, 20, 0),
|
|
|
|
box(ids.line1, 0, 40),
|
|
|
|
box(ids.boxD, 20, 40),
|
|
|
|
box(ids.boxX, 50, 20, 30),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(65, 25, ids.boxX).pointerMove(16, 25, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 0, y: 20 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(gapLines).toHaveLength(2)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
expect(gapLines[1].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
// check outer edge snaps too
|
|
|
|
expect(pointLines).toHaveLength(2)
|
|
|
|
expect(pointLines[0].points).toHaveLength(6)
|
|
|
|
expect(pointLines[1].points).toHaveLength(6)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should not snap horizontally if the shape is larger than the gap', () => {
|
|
|
|
// ┌─────┐ ┌─────┐
|
|
|
|
// │ │ │ │
|
|
|
|
// │ A │ │ B │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// ┌──────┼─────┼─────────────┼─────┼──────┐
|
|
|
|
// │ │ │ │ │ │
|
|
|
|
// │ │ │ X │ │ │ ◄─── drag
|
|
|
|
// │ │ │ │ │ │
|
|
|
|
// └──────┼─────┼─────────────┼─────┼──────┘
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// └─────┘ └─────┘
|
|
|
|
//
|
|
|
|
// no snap to center gap between A + B
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 20, 0, 10, 100),
|
|
|
|
box(ids.box2, 70, 0, 10, 100),
|
|
|
|
box(ids.boxX, 0, 50, 100, 10),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(50, 55, ids.boxX).pointerMove(51, 66, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 1, y: 61 })
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()?.length).toBe(0)
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should work if the thing being dragged is a selection', () => {
|
|
|
|
// selection
|
|
|
|
// ┌─────────────────────────┐
|
|
|
|
// │ │ ┌────────┐
|
|
|
|
// ┌────────┐ │ ┌────────────┐ │ │ │
|
|
|
|
// │ │ │ │ │ │ │ │
|
|
|
|
// │ │ │ │ C │ │ │ │
|
|
|
|
// │ A ├───┼───┤ ┌────┐ └────────────┘ ├───┼───┤ B │
|
|
|
|
// │ │ │ │ │ │ │ │
|
|
|
|
// │ │ │ │ D │ │ │ │
|
|
|
|
// └────────┘ │ └────┘ │ └────────┘
|
|
|
|
// └─────────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 0, 50, 50, 100),
|
|
|
|
box(ids.box2, 350, 0, 50, 100),
|
|
|
|
box(ids.line1, 200, 10, 100, 10),
|
|
|
|
box(ids.boxD, 100, 80, 10, 50),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.line1, ids.boxD)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(200, 50, ids.line1).pointerMove(201, 61, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.line1)).toMatchObject({ x: 200, y: 21 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(pointLines).toHaveLength(0)
|
|
|
|
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
const sortedGaps = gapLines[0].gaps.sort((a, b) => a.startEdge[0].x - b.startEdge[0].x)
|
|
|
|
|
|
|
|
expect(sortedGaps[0].startEdge[0].x).toBeCloseTo(50)
|
|
|
|
expect(sortedGaps[0].endEdge[0].x).toBeCloseTo(100)
|
|
|
|
|
|
|
|
expect(sortedGaps[1].startEdge[0].x).toBeCloseTo(300)
|
|
|
|
expect(sortedGaps[1].endEdge[0].x).toBeCloseTo(350)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('Snap-next-to behavior', () => {
|
|
|
|
beforeEach(() => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor?.dispose()
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
it('snaps a shape to the left of two others, matching the gap size', () => {
|
|
|
|
// ┌───┐
|
|
|
|
// │ X │
|
|
|
|
// └───┘ ┌───┐ ┌───┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └───┘ └───┘
|
|
|
|
// │
|
|
|
|
// │ drag x down
|
|
|
|
// ▼
|
|
|
|
//
|
|
|
|
// ┌───┐ ┌───┐ ┌───┐
|
|
|
|
// │ X ├────┼────┤ A ├────┼────┤ B │ *snap*
|
|
|
|
// └───┘ └───┘ └───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.boxX, 0, 0), box(ids.box1, 50, 10), box(ids.box2, 100, 10)])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 5, ids.boxX).pointerMove(6, 16, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 0, y: 10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('expands the selection to the right for left snap-besides ', () => {
|
|
|
|
// ┌───┐
|
|
|
|
// │ X │
|
|
|
|
// └───┘ ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
|
|
|
// │ A │ │ B │ │ C │ │ D │
|
|
|
|
// └───┘ └───┘ └───┘ └───┘
|
|
|
|
// │
|
|
|
|
// │ drag x down
|
|
|
|
// ▼
|
|
|
|
//
|
|
|
|
// ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
|
|
|
// │ X ├────┼────┤ A ├────┼────┤ B ├────┼────┤ C ├────┼────┤ D │
|
|
|
|
// └───┘ └───┘ └───┘ └───┘ └───┘
|
|
|
|
//
|
|
|
|
// *snap*
|
|
|
|
//
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.boxX, 0, 0),
|
|
|
|
box(ids.box1, 50, 10),
|
|
|
|
box(ids.box2, 100, 10),
|
|
|
|
box(ids.line1, 150, 10),
|
|
|
|
box(ids.boxD, 200, 10),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 5, ids.boxX).pointerMove(6, 16, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 0, y: 10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(4)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('snaps a shape to the right of two others, matching the gap size', () => {
|
|
|
|
// ┌───┐
|
|
|
|
// │ X │
|
|
|
|
// ┌───┐ ┌───┐ └───┘
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └───┘ └───┘
|
|
|
|
// │
|
|
|
|
// │ drag X down
|
|
|
|
// ▼
|
|
|
|
//
|
|
|
|
// ┌───┐ ┌───┐ ┌───┐
|
|
|
|
// │ A ├────┼────┤ B ├────┼────┤ X │ *snap*
|
|
|
|
// └───┘ └───┘ └───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.box1, 0, 10), box(ids.box2, 50, 10), box(ids.boxX, 100, 0)])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(105, 5, ids.boxX).pointerMove(106, 16, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 100, y: 10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
it('expands the selection to the left for right snap-besides ', () => {
|
|
|
|
// ┌───┐
|
|
|
|
// │ X │
|
|
|
|
// ┌───┐ ┌───┐ ┌───┐ ┌───┐ └───┘
|
|
|
|
// │ A │ │ B │ │ C │ │ D │
|
|
|
|
// └───┘ └───┘ └───┘ └───┘
|
|
|
|
// │
|
|
|
|
// drag x down │
|
|
|
|
// ▼
|
|
|
|
//
|
|
|
|
// ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
|
|
|
// │ A ├────┼────┤ B ├────┼────┤ C ├────┼────┤ D ├────┼────┤ x │
|
|
|
|
// └───┘ └───┘ └───┘ └───┘ └───┘
|
|
|
|
//
|
|
|
|
// *snap*
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 0, 10),
|
|
|
|
box(ids.box2, 50, 10),
|
|
|
|
box(ids.line1, 100, 10),
|
|
|
|
box(ids.boxD, 150, 10),
|
|
|
|
box(ids.boxX, 200, 0),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(205, 5, ids.boxX).pointerMove(206, 16, { ctrlKey: true })
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 200, y: 10 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(4)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
it('snaps a shape above two others, matching the gap size', () => {
|
|
|
|
// ┌───┐ ┌───┐
|
|
|
|
// │ X │ │ X │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// drag X ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ A │ ────► │ A │ *snap*
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ B │ │ B │
|
|
|
|
// └───┘ └───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.boxX, 0, 0), box(ids.box1, 10, 20), box(ids.box2, 10, 40)])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 5, ids.boxX).pointerMove(16, 6, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 10, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('expands the selection downwards for top snap-besides ', () => {
|
|
|
|
// ┌───┐ ┌───┐
|
|
|
|
// │ X │ │ X │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// drag X ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ A │ ────► │ A │ *snap*
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ B │ │ B │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ C │ │ C │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ D │ │ D │
|
|
|
|
// └───┘ └───┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.boxX, 0, 0),
|
|
|
|
box(ids.box1, 10, 20),
|
|
|
|
box(ids.box2, 10, 40),
|
|
|
|
box(ids.line1, 10, 60),
|
|
|
|
box(ids.boxD, 10, 80),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 5, ids.boxX).pointerMove(16, 6, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 10, y: 0 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(4)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('snaps a shape below two others, matching the gap size', () => {
|
|
|
|
// ┌───┐ ┌───┐
|
|
|
|
// │ A │ │ A │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ B │ │ B │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ drag X ┌─┴─┐ *snap*
|
|
|
|
// │ X │ │ X │
|
|
|
|
// └───┘ ────► └───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.box1, 10, 0), box(ids.box2, 10, 20), box(ids.boxX, 0, 40)])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 45, ids.boxX).pointerMove(16, 46, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 10, y: 40 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('expands the selection upwards for bottom snap-besides ', () => {
|
|
|
|
// ┌───┐ ┌───┐
|
|
|
|
// │ A │ │ A │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ B │ │ B │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ C │ │ C │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ ┌─┴─┐
|
|
|
|
// │ D │ │ D │
|
|
|
|
// └───┘ └─┬─┘
|
|
|
|
// ┼
|
|
|
|
// ┌───┐ drag X ┌─┴─┐ *snap*
|
|
|
|
// │ X │ │ X │
|
|
|
|
// └───┘ ────► └───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 10, 0),
|
|
|
|
box(ids.box2, 10, 20),
|
|
|
|
box(ids.line1, 10, 40),
|
|
|
|
box(ids.boxD, 10, 60),
|
|
|
|
box(ids.boxX, 0, 80),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(5, 85, ids.boxX).pointerMove(16, 86, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxX)).toMatchObject({ x: 10, y: 80 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(4)
|
|
|
|
|
|
|
|
// also check the outer edge snaps
|
|
|
|
expect(pointLines).toHaveLength(3)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should work if the thing being dragged is a selection', () => {
|
|
|
|
// selection
|
|
|
|
// ┌─────────────────────────┐
|
|
|
|
// │ │
|
|
|
|
// ┌────────┐ ┌────────┐ │ ┌────────────┐ │
|
|
|
|
// │ │ │ │ │ │ C │ │
|
|
|
|
// │ │ │ │ │ │ │ │
|
|
|
|
// │ A ├───┼───┤ B ├───┼───┤ ┌────┐ └────────────┘ │
|
|
|
|
// │ │ │ │ │ │ D │ │
|
|
|
|
// │ │ │ │ │ │ │ │
|
|
|
|
// └────────┘ └────────┘ │ └────┘ │
|
|
|
|
// └─────────────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 0, 50, 50, 100),
|
|
|
|
box(ids.box2, 100, 50, 50, 100),
|
|
|
|
box(ids.line1, 300, 10, 100, 10),
|
|
|
|
box(ids.boxD, 200, 80, 10, 50),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.line1, ids.boxD)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(300, 50, ids.line1).pointerMove(301, 101, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.boxD)).toMatchObject({ x: 200, y: 131 })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
const { gapLines, pointLines } = getGapAndPointLines(editor.snaps.getLines()!)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
expect(gapLines).toHaveLength(1)
|
|
|
|
expect(pointLines).toHaveLength(0)
|
|
|
|
|
|
|
|
expect(gapLines[0].gaps).toHaveLength(2)
|
|
|
|
|
|
|
|
const sortedGaps = gapLines[0].gaps.sort((a, b) => a.startEdge[0].x - b.startEdge[0].x)
|
|
|
|
|
|
|
|
expect(sortedGaps[0].startEdge[0].x).toBeCloseTo(50)
|
|
|
|
expect(sortedGaps[0].endEdge[0].x).toBeCloseTo(100)
|
|
|
|
|
|
|
|
expect(sortedGaps[1].startEdge[0].x).toBeCloseTo(150)
|
|
|
|
expect(sortedGaps[1].endEdge[0].x).toBeCloseTo(200)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('translating while the grid is enabled', () => {
|
|
|
|
it('does not snap to the grid', () => {
|
|
|
|
// 0 20 50 70
|
|
|
|
// ┌───┐ ┌───┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// └───┘ └───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.box1, 0, 0, 20, 20), box(ids.box2, 50, 0, 20, 20)])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-07-18 21:50:23 +00:00
|
|
|
editor.updateInstanceState({ isGridMode: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// try to snap A to B
|
|
|
|
// doesn't work because of the grid
|
|
|
|
|
|
|
|
// 0 20 50 70
|
|
|
|
// ┌───┬┬───┐
|
|
|
|
// │ A ││ B │
|
|
|
|
// └───┴┴───┘
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1).pointerDown(10, 10, ids.box1).pointerMove(39, 10)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// rounds to nearest 10
|
2023-08-02 18:12:25 +00:00
|
|
|
expect(editor.getShapePageBounds(ids.box1)!.x).toEqual(30)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// engage snap mode and it should indeed snap to B
|
|
|
|
|
|
|
|
// 0 20 50 70
|
|
|
|
// ┌───┬───┐
|
|
|
|
// │ A │ B │
|
|
|
|
// └───┴───┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.keyDown('Control')
|
2023-08-02 18:12:25 +00:00
|
|
|
expect(editor.getShapePageBounds(ids.box1)!.x).toEqual(30)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// and we can move the box anywhere if there are no snaps nearby
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerMove(-19, -32, { ctrlKey: true })
|
2023-08-02 18:12:25 +00:00
|
|
|
expect(editor.getShapePageBounds(ids.box1)!).toMatchObject({ x: -29, y: -42 })
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('snap lines', () => {
|
|
|
|
it('should show up for all matching snaps, even if the axis is locked', () => {
|
|
|
|
// 0 60 200
|
|
|
|
//
|
|
|
|
// ┌─────────────┐ ┌─────────────┐
|
|
|
|
// │ A │ │ B │
|
|
|
|
// │ │ │ │
|
|
|
|
// ◄──────── │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ │ │ │
|
|
|
|
// 100 └─────────────┘ └─────────────┘
|
|
|
|
//
|
|
|
|
// hold shift and
|
|
|
|
// drag A left to C
|
|
|
|
//
|
|
|
|
// 200 ┌─────────────┐
|
|
|
|
// │ C │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// └─────────────┘
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// ────────────────────────────────────────────────────────
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// 0 *snap* 100 200
|
|
|
|
//
|
|
|
|
// x─────────────x──────────────────x─────────────x
|
|
|
|
// │ A │ │ B │
|
|
|
|
// │ │ │ │
|
|
|
|
// │ x──────┼──────────────────┼──────x │
|
|
|
|
// │ │ │ │ │
|
|
|
|
// │ │ │ │ │
|
|
|
|
// 100 x──────┼──────x──────────────────x─────────────x
|
|
|
|
// │ │ │
|
|
|
|
// │ │ │
|
|
|
|
// │ │ │
|
|
|
|
// │ │ │
|
|
|
|
// │ │ │
|
|
|
|
// 200 x──────┼──────x
|
|
|
|
// │ C │ │
|
|
|
|
// │ │ │
|
|
|
|
// │ x │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// x─────────────x
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
box(ids.box1, 60, 0, 100, 100),
|
|
|
|
box(ids.box2, 200, 0, 100, 100),
|
|
|
|
box(ids.line1, 0, 200, 100, 100),
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor
|
2023-04-25 11:01:25 +00:00
|
|
|
.select(ids.box1)
|
|
|
|
.pointerDown(110, 50, ids.box1)
|
|
|
|
.pointerMove(49, 52, { shiftKey: true, ctrlKey: true })
|
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)).toMatchObject({
|
2023-04-25 11:01:25 +00:00
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
props: { w: 100, h: 100 },
|
|
|
|
})
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
expect(getSnapLines(editor)).toMatchInlineSnapshot(`
|
2024-01-31 16:53:40 +00:00
|
|
|
[
|
2023-04-25 11:01:25 +00:00
|
|
|
"0,0 0,100 0,200 0,300",
|
|
|
|
"0,0 100,0 200,0 300,0",
|
|
|
|
"0,100 100,100 200,100 300,100",
|
|
|
|
"100,0 100,100 100,200 100,300",
|
|
|
|
"50,50 250,50",
|
|
|
|
"50,50 50,250",
|
|
|
|
]
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('translating a shape with a child', () => {
|
|
|
|
it('should not snap to the child', () => {
|
|
|
|
// 0 1 11 50
|
|
|
|
// ┌───────────────────┐
|
|
|
|
// │ ┌───┐ │
|
|
|
|
// │ │ B │ │
|
|
|
|
// │ └───┘ │
|
|
|
|
// │ │
|
|
|
|
// │ A │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// │ │
|
|
|
|
// └───────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.box1, 0, 0, 50, 50), box(ids.box2, 1, 1)])
|
|
|
|
editor.updateShapes([{ id: ids.box2, type: 'geo', parentId: ids.box1 }])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(25, 25, ids.box1).pointerMove(50, 25, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines()?.length).toBe(0)
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)).toMatchObject({
|
2023-04-25 11:01:25 +00:00
|
|
|
x: 25,
|
|
|
|
y: 0,
|
|
|
|
props: { w: 50, h: 50 },
|
|
|
|
})
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box2)).toMatchObject({ x: 1, y: 1, props: { w: 10, h: 10 } })
|
2023-08-02 18:12:25 +00:00
|
|
|
expect(editor.getShapePageBounds(ids.box2)).toMatchObject({
|
2023-04-25 11:01:25 +00:00
|
|
|
x: 26,
|
|
|
|
y: 1,
|
|
|
|
w: 10,
|
|
|
|
h: 10,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('translating a shape with a bound shape', () => {
|
|
|
|
it('should not snap to arrows', () => {
|
|
|
|
// 100 200
|
|
|
|
// ┌───────────────────┐
|
|
|
|
// │ ┌───┐ ┌───┐ │
|
|
|
|
// │ │ A │ ---> │ B │ │
|
|
|
|
// │ └───┘ └───┘ │
|
|
|
|
// └───────────────────┘
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([box(ids.box1, 0, 0, 100, 100), box(ids.box2, 200, 0, 100, 100)])
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// Create an arrow starting within the first box and ending within the second box
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
editor.setCurrentTool('arrow').pointerDown(50, 50).pointerMove(250, 50).pointerUp()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// 100 200
|
|
|
|
// ┌───────────────────┐
|
|
|
|
// │ ┌───┐ │
|
|
|
|
// │ , │ B │ │
|
|
|
|
// │ ┌───┐ └───┘ │
|
|
|
|
// | │ A │ |
|
|
|
|
// | └───┘ |
|
|
|
|
// └───────────────────┘
|
|
|
|
|
2023-11-13 12:42:07 +00:00
|
|
|
expect(editor.getShape(editor.getSelectedShapeIds()[0])?.type).toBe('arrow')
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(50, 50, ids.box1).pointerMove(84, 110, { ctrlKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-11-16 12:07:33 +00:00
|
|
|
expect(editor.snaps.getLines().length).toBe(0)
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should preserve arrow bindings', () => {
|
2023-06-03 20:46:53 +00:00
|
|
|
const arrow1 = createShapeId('arrow1')
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ id: ids.box1, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } },
|
|
|
|
{ id: ids.box2, type: 'geo', x: 300, y: 300, props: { w: 100, h: 100 } },
|
|
|
|
{
|
|
|
|
id: arrow1,
|
|
|
|
type: 'arrow',
|
|
|
|
x: 150,
|
|
|
|
y: 150,
|
|
|
|
props: {
|
|
|
|
start: {
|
|
|
|
type: 'binding',
|
|
|
|
isExact: false,
|
|
|
|
boundShapeId: ids.box1,
|
|
|
|
normalizedAnchor: { x: 0.5, y: 0.5 },
|
2023-12-01 21:34:12 +00:00
|
|
|
isPrecise: false,
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
|
|
|
end: {
|
|
|
|
type: 'binding',
|
|
|
|
isExact: false,
|
|
|
|
boundShapeId: ids.box2,
|
|
|
|
normalizedAnchor: { x: 0.5, y: 0.5 },
|
2023-12-01 21:34:12 +00:00
|
|
|
isPrecise: false,
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1, arrow1)
|
|
|
|
editor.pointerDown(150, 150, ids.box1).pointerMove(0, 0)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: -50, y: -50 })
|
|
|
|
expect(editor.getShape(arrow1)).toMatchObject({
|
2023-04-25 11:01:25 +00:00
|
|
|
props: { start: { type: 'binding' }, end: { type: 'binding' } },
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('breaks arrow bindings when cloning', () => {
|
2023-06-03 20:46:53 +00:00
|
|
|
const arrow1 = createShapeId('arrow1')
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{ id: ids.box1, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } },
|
|
|
|
{ id: ids.box2, type: 'geo', x: 300, y: 300, props: { w: 100, h: 100 } },
|
|
|
|
{
|
|
|
|
id: arrow1,
|
|
|
|
type: 'arrow',
|
|
|
|
x: 150,
|
|
|
|
y: 150,
|
|
|
|
props: {
|
|
|
|
start: {
|
|
|
|
type: 'binding',
|
|
|
|
isExact: false,
|
|
|
|
boundShapeId: ids.box1,
|
|
|
|
normalizedAnchor: { x: 0.5, y: 0.5 },
|
2023-12-01 21:34:12 +00:00
|
|
|
isPrecise: false,
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
|
|
|
end: {
|
|
|
|
type: 'binding',
|
|
|
|
isExact: false,
|
|
|
|
boundShapeId: ids.box2,
|
|
|
|
normalizedAnchor: { x: 0.5, y: 0.5 },
|
2023-12-01 21:34:12 +00:00
|
|
|
isPrecise: false,
|
2023-04-25 11:01:25 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.select(ids.box1, arrow1)
|
|
|
|
editor.pointerDown(150, 150, ids.box1).pointerMove(0, 0, { altKey: true })
|
2023-04-25 11:01:25 +00:00
|
|
|
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 100, y: 100 })
|
|
|
|
expect(editor.getShape(arrow1)).toMatchObject({
|
2023-04-25 11:01:25 +00:00
|
|
|
props: { start: { type: 'binding' }, end: { type: 'binding' } },
|
|
|
|
})
|
|
|
|
|
2023-11-14 16:32:27 +00:00
|
|
|
const newArrow = editor
|
|
|
|
.getCurrentPageShapes()
|
|
|
|
.find((s) => editor.isShapeOfType<TLArrowShape>(s, 'arrow') && s.id !== arrow1)
|
2023-04-25 11:01:25 +00:00
|
|
|
expect(newArrow).toMatchObject({
|
|
|
|
props: { start: { type: 'binding' }, end: { type: 'point' } },
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('When dragging a shape onto a parent', () => {
|
|
|
|
it('reparents the shape', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.frame1,
|
|
|
|
type: 'frame',
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
props: {
|
|
|
|
w: 200,
|
|
|
|
h: 200,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 500,
|
|
|
|
y: 500,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(550, 550, ids.box1).pointerMove(100, 100).pointerUp()
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
expect(editor.getShape(ids.box1)?.parentId).toBe(ids.frame1)
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('does not reparent the shape when the parent is clipped', () => {
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.createShapes([
|
2023-04-25 11:01:25 +00:00
|
|
|
{
|
|
|
|
id: ids.frame1,
|
|
|
|
type: 'frame',
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
props: {
|
|
|
|
w: 200,
|
|
|
|
h: 200,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.frame2,
|
|
|
|
type: 'frame',
|
|
|
|
x: 200,
|
|
|
|
y: 200,
|
|
|
|
props: {
|
|
|
|
w: 500,
|
|
|
|
h: 500,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: ids.box1,
|
|
|
|
type: 'geo',
|
|
|
|
x: 500,
|
|
|
|
y: 500,
|
|
|
|
props: {
|
|
|
|
w: 100,
|
|
|
|
h: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
|
|
|
|
// drop the frame2 onto frame 1
|
`ShapeUtil.getGeometry`, selection rewrite (#1751)
This PR is a significant rewrite of our selection / hit testing logic.
It
- replaces our current geometric helpers (`getBounds`, `getOutline`,
`hitTestPoint`, and `hitTestLineSegment`) with a new geometry API
- moves our hit testing entirely to JS using geometry
- improves selection logic, especially around editing shapes, groups and
frames
- fixes many minor selection bugs (e.g. shapes behind frames)
- removes hit-testing DOM elements from ShapeFill etc.
- adds many new tests around selection
- adds new tests around selection
- makes several superficial changes to surface editor APIs
This PR is hard to evaluate. The `selection-omnibus` test suite is
intended to describe all of the selection behavior, however all existing
tests are also either here preserved and passing or (in a few cases
around editing shapes) are modified to reflect the new behavior.
## Geometry
All `ShapeUtils` implement `getGeometry`, which returns a single
geometry primitive (`Geometry2d`). For example:
```ts
class BoxyShapeUtil {
getGeometry(shape: BoxyShape) {
return new Rectangle2d({
width: shape.props.width,
height: shape.props.height,
isFilled: true,
margin: shape.props.strokeWidth
})
}
}
```
This geometric primitive is used for all bounds calculation, hit
testing, intersection with arrows, etc.
There are several geometric primitives that extend `Geometry2d`:
- `Arc2d`
- `Circle2d`
- `CubicBezier2d`
- `CubicSpline2d`
- `Edge2d`
- `Ellipse2d`
- `Group2d`
- `Polygon2d`
- `Rectangle2d`
- `Stadium2d`
For shapes that have more complicated geometric representations, such as
an arrow with a label, the `Group2d` can accept other primitives as its
children.
## Hit testing
Previously, we did all hit testing via events set on shapes and other
elements. In this PR, I've replaced those hit tests with our own
calculation for hit tests in JavaScript. This removed the need for many
DOM elements, such as hit test area borders and fills which only existed
to trigger pointer events.
## Selection
We now support selecting "hollow" shapes by clicking inside of them.
This involves a lot of new logic but it should work intuitively. See
`Editor.getShapeAtPoint` for the (thoroughly commented) implementation.
![Kapture 2023-07-23 at 23 27
27](https://github.com/tldraw/tldraw/assets/23072548/a743275c-acdb-42d9-a3fe-b3e20dce86b6)
every sunset is actually the sun hiding in fear and respect of tldraw's
quality of interactions
This PR also fixes several bugs with scribble selection, in particular
around the shift key modifier.
![Kapture 2023-07-24 at 23 34
07](https://github.com/tldraw/tldraw/assets/23072548/871d67d0-8d06-42ae-a2b2-021effba37c5)
...as well as issues with labels and editing.
There are **over 100 new tests** for selection covering groups, frames,
brushing, scribbling, hovering, and editing. I'll add a few more before
I feel comfortable merging this PR.
## Arrow binding
Using the same "hollow shape" logic as selection, arrow binding is
significantly improved.
![Kapture 2023-07-22 at 07 46
25](https://github.com/tldraw/tldraw/assets/23072548/5aa724b3-b57d-4fb7-92d0-80e34246753c)
a thousand wise men could not improve on this
## Moving focus between editing shapes
Previously, this was handled in the `editing_shapes` state. This is
moved to `useEditableText`, and should generally be considered an
advanced implementation detail on a shape-by-shape basis. This addresses
a bug that I'd never noticed before, but which can be reproduced by
selecting an shape—but not focusing its input—while editing a different
shape. Previously, the new shape became the editing shape but its input
did not focus.
![Kapture 2023-07-23 at 23 19
09](https://github.com/tldraw/tldraw/assets/23072548/a5e157fb-24a8-42bd-a692-04ce769b1a9c)
In this PR, you can select a shape by clicking on its edge or body, or
select its input to transfer editing / focus.
![Kapture 2023-07-23 at 23 22
21](https://github.com/tldraw/tldraw/assets/23072548/7384e7ea-9777-4e1a-8f63-15de2166a53a)
tldraw, glorious tldraw
### Change Type
- [x] `major` — Breaking change
### Test Plan
1. Erase shapes
2. Select shapes
3. Calculate their bounding boxes
- [ ] Unit Tests // todo
- [ ] End to end tests // todo
### Release Notes
- [editor] Remove `ShapeUtil.getBounds`, `ShapeUtil.getOutline`,
`ShapeUtil.hitTestPoint`, `ShapeUtil.hitTestLineSegment`
- [editor] Add `ShapeUtil.getGeometry`
- [editor] Add `Editor.getShapeGeometry`
2023-07-25 16:10:15 +00:00
|
|
|
editor.reparentShapes([ids.frame2], ids.frame1)
|
|
|
|
expect(editor.getShape(ids.frame2)?.parentId).toBe(ids.frame1)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// drop box1 onto the CLIPPED part of frame2
|
2023-06-02 15:21:45 +00:00
|
|
|
editor.pointerDown(550, 550, ids.box1).pointerMove(350, 350).pointerUp()
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
// It should not become the child of frame2 because it is clipped
|
2023-11-16 15:34:56 +00:00
|
|
|
expect(editor.getShape(ids.box1)?.parentId).toBe(editor.getCurrentPageId())
|
2023-04-25 11:01:25 +00:00
|
|
|
})
|
|
|
|
})
|
2023-08-05 11:21:07 +00:00
|
|
|
|
|
|
|
describe('When dragging shapes', () => {
|
|
|
|
it('should drag and undo and redo', () => {
|
2023-11-14 16:32:27 +00:00
|
|
|
editor.deleteShapes(editor.getCurrentPageShapes())
|
2023-08-05 11:21:07 +00:00
|
|
|
|
|
|
|
editor.setCurrentTool('arrow').pointerMove(0, 0).pointerDown().pointerMove(100, 100).pointerUp()
|
|
|
|
|
|
|
|
editor.expectShapeToMatch({
|
2023-11-14 16:32:27 +00:00
|
|
|
id: editor.getCurrentPageShapes()[0]!.id,
|
2023-08-05 11:21:07 +00:00
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
})
|
|
|
|
|
|
|
|
editor.setCurrentTool('geo').pointerMove(-10, 100).pointerDown().pointerUp()
|
|
|
|
|
|
|
|
editor.expectShapeToMatch({
|
2023-11-14 16:32:27 +00:00
|
|
|
id: editor.getCurrentPageShapes()[1]!.id,
|
2023-08-05 11:21:07 +00:00
|
|
|
x: -110,
|
|
|
|
y: 0,
|
|
|
|
})
|
|
|
|
|
|
|
|
editor
|
|
|
|
.selectAll()
|
|
|
|
.pointerMove(50, 50)
|
|
|
|
.pointerDown()
|
|
|
|
.pointerMove(100, 50)
|
|
|
|
.pointerUp()
|
|
|
|
.expectShapeToMatch({
|
2023-11-14 16:32:27 +00:00
|
|
|
id: editor.getCurrentPageShapes()[0]!.id,
|
2023-08-05 11:21:07 +00:00
|
|
|
x: 50, // 50 to the right
|
|
|
|
y: 0,
|
|
|
|
})
|
|
|
|
.expectShapeToMatch({
|
2023-11-14 16:32:27 +00:00
|
|
|
id: editor.getCurrentPageShapes()[1]!.id,
|
2023-08-05 11:21:07 +00:00
|
|
|
x: -60, // 50 to the right
|
|
|
|
y: 0,
|
|
|
|
})
|
|
|
|
|
|
|
|
editor
|
|
|
|
.undo()
|
|
|
|
.expectShapeToMatch({
|
2023-11-14 16:32:27 +00:00
|
|
|
id: editor.getCurrentPageShapes()[0]!.id,
|
2023-08-05 11:21:07 +00:00
|
|
|
x: 0, // 50 to the right
|
|
|
|
y: 0,
|
|
|
|
})
|
|
|
|
.expectShapeToMatch({
|
2023-11-14 16:32:27 +00:00
|
|
|
id: editor.getCurrentPageShapes()[1]!.id,
|
2023-08-05 11:21:07 +00:00
|
|
|
x: -110, // 50 to the right
|
|
|
|
y: 0,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2023-08-15 16:25:50 +00:00
|
|
|
|
|
|
|
it('clones a single shape simply', () => {
|
|
|
|
editor
|
|
|
|
// create a note shape
|
|
|
|
.setCurrentTool('note')
|
|
|
|
.pointerMove(50, 50)
|
|
|
|
.click()
|
|
|
|
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getOnlySelectedShape()).toBe(editor.getCurrentPageShapes()[0])
|
|
|
|
expect(editor.getHoveredShape()).toBe(editor.getCurrentPageShapes()[0])
|
2023-08-15 16:25:50 +00:00
|
|
|
|
|
|
|
// click on the canvas to deselect
|
|
|
|
editor.pointerMove(200, 50).click()
|
|
|
|
|
2023-11-13 14:31:27 +00:00
|
|
|
expect(editor.getOnlySelectedShape()).toBe(null)
|
2023-11-14 10:23:03 +00:00
|
|
|
expect(editor.getHoveredShape()).toBe(undefined)
|
2023-08-15 16:25:50 +00:00
|
|
|
|
|
|
|
// move back over the the shape
|
|
|
|
editor.pointerMove(50, 50)
|
|
|
|
|
2023-11-13 14:31:27 +00:00
|
|
|
expect(editor.getOnlySelectedShape()).toBe(null)
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getHoveredShape()).toBe(editor.getCurrentPageShapes()[0])
|
2023-08-15 16:25:50 +00:00
|
|
|
|
|
|
|
// start dragging the shape
|
|
|
|
editor
|
|
|
|
.pointerDown()
|
|
|
|
.pointerMove(50, 500)
|
|
|
|
// start cloning
|
|
|
|
.keyDown('Alt')
|
|
|
|
// stop dragging
|
|
|
|
.pointerUp()
|
|
|
|
|
2023-11-14 16:32:27 +00:00
|
|
|
expect(editor.getCurrentPageShapes()).toHaveLength(2)
|
|
|
|
const [, sticky2] = editor.getCurrentPageShapes()
|
2023-11-13 14:31:27 +00:00
|
|
|
expect(editor.getOnlySelectedShape()).toBe(sticky2)
|
2023-11-13 16:02:50 +00:00
|
|
|
expect(editor.getEditingShape()).toBe(undefined)
|
2023-11-14 10:23:03 +00:00
|
|
|
expect(editor.getHoveredShape()).toBe(sticky2)
|
2023-08-15 16:25:50 +00:00
|
|
|
})
|