tldraw / tldraw

SDK for creating whiteboards and canvas experiences on the web.
https://tldraw.dev
Other
33.4k stars 1.99k forks source link

[feature] Add grids #344

Closed fnky closed 2 years ago

fnky commented 2 years ago

Adds support for adding grids of various colors and sizes to the renderer.

It also implements the grid into the app, which can be toggled from the Preferences menu and with the shortcut Ctrl+G/Command+G.

A possible improvement would be to add/remove grids from UI.

API Changes

Adds an optional grids prop to the Renderer and Canvas components, which takes a list of TLGrid objects:

<Renderer
  grids={[
    { color: 'rgba(255, 0, 0, 0.2)', size: 20 },
    { color: 'rgba(255, 0, 0, 0.1)', size: 10 }
  ]}
/>

This feature doesn't implement grid snapping for transforms and translations of layers, though that would be a great feature to add.

vercel[bot] commented 2 years ago

@fnky is attempting to deploy a commit to the tldraw Team on Vercel.

A member of the Team first needs to authorize it.

vercel[bot] commented 2 years ago

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployments, click below or on the icon next to each commit.

core – ./

🔍 Inspect: https://vercel.com/tldraw/core/7BqaFHtAeBzi6u5LdwPnvgfJshZ4
✅ Preview: https://core-git-fork-fnky-feature-grids-tldraw.vercel.app

tldraw – ./

🔍 Inspect: https://vercel.com/tldraw/tldraw/3pP5eLFsJVj1ucWiJii1CUMVJAX1
✅ Preview: https://tldraw-git-fork-fnky-feature-grids-tldraw.vercel.app

Proful commented 2 years ago

Looking very good 👌 May be snap to Grid next :)

image

steveruizok commented 2 years ago

This is awesome!

Some initial thoughts:

Excellent use of the <pattern element.

Do we need to make the renderer support an unlimited number of grids? I can't think of many apps where a "major" and "minor" grid wouldn't be sufficient. This could simplify the props (gridMajor: number and gridMinor: number) and avoid needing to memoize the arrays outside of the Renderer. We already have a theme object, so we could add gridMajor and gridMinor there as well.

This feature doesn't implement grid snapping for transforms and translations of layers, though that would be a great feature to add.

I think this would make sense as something to work out as part of this PR, just because it could shape the way we implement the grid in the core library, too.

Snapping to Grid

Throughout the app's dragging interactions, we use "delta"s to decide where the dragged shapes should go. A delta is the vector between the place in the document where the user started pointing, or the originPoint, and the place where their pointer is now, or the currentPoint.

Features like snapping or "axis" dragging are easily implemented by adjusting this delta.

let delta = Vec.sub(currentPoint, originPoint)

if (shiftKey) {
  if (Math.abs(delta[0]) < Math.abs(delta[1])) {
    delta[0] = 0
  } else {
    delta[1] = 0
  }
}

In this case, I we would need to make sure that the result after the delta is a point on the grid, so we would round both the originPoint and the currentPoint to be on the grid.

let delta = app.appState.grid 
  ? Vec.sub(TLDR.roundToGrid(currentPoint), TLDR.roundToGrid(originPoint)) 
  : Vec.sub(currentPoint, originPoint)

I'll dig into the code to check for any problems but on first look I feel really positive about this getting into the core lib.

fnky commented 2 years ago

@steveruizok Thanks!

Initially I had a "grid" and "subgrid" and changed it to take more grids similar to Figma. But I agree that we could simplify this by just having two. I like your "major" and "minor" terminology here.

As for snapping, I agree it makes sense to implement it as part of this PR. Your insight helps a lot here, as I was a bit lost in where to put this.

As for the UX, there are different approaches to this. There are at least three approaches I can think of on top of my head:

  1. Always snap objects to grid lines, with no intermediate "non-snapping" movement.
  2. Snap objects to grid based on some distance to the nearest grid line. This is similar to how grid snapping works in Figma and Sketch, which allows objects to be moved more precisely when zoomed out, without having to toggle the snapping.
  3. A combination of both, where by default if the grid is visible, moving objects will snap with behavior 3 and holding a modifier key (e.g. SHIFT) will change the behavior for 1.
fnky commented 2 years ago

Had a look into implementing the grid snap and found that using the center point of the shape wouldn't be enough to snap it to the grid lines.

I believe this could be solved by using the edges of the shape, such we can snap by the top (in Y coordinate) and left (in X coordinate) edges when translating, and snap the grabbed edge when resizing/transforming the shape.

I'll dig more into the code to get a better understanding of how this could be done.

steveruizok commented 2 years ago

Hey, doing some more work on this and looking at different ways to present grids that scale with the zoom level.

Kapture 2021-11-24 at 20 48 09

steveruizok commented 2 years ago

Kapture 2021-11-24 at 21 07 47

A couple of changes:

fnky commented 2 years ago

@steveruizok Looks really neat, really nice improvement!

I haven't had time to work much on grid snapping yet, but hopefully will be able to look into it in the weekend. If someone else finds time to take a stab at it, don't hesitate :)

steveruizok commented 2 years ago

Blocked at the moment by svg pattern fill not working on iOS. Not sure what the fix there is but would love to hear ideas!

fnky commented 2 years ago

Blocked at the moment by svg pattern fill not working on iOS. Not sure what the fix there is but would love to hear ideas!

Huh, that's odd. Seems like it needs the entire path and not just the fragment. Will this work?:

<rect fill={`url('${location.href}#grid-${i}')`} />
steveruizok commented 2 years ago

SVG issue solved! Ready for testing.

fnky commented 2 years ago

@steveruizok Awesome work! Appreciate your hard work on this. Love the dot grid; makes much more sense for tldraw.

Tried on iOS too and it works really well.

Can't thank you enough!

steveruizok commented 2 years ago

I'm going to implement snapping to grid for arrow handles before we merge.

Still not sure whether to have individual shapes snap to grid while resizing a selection. 🤔

steveruizok commented 2 years ago

This PR now also brings vec and intersect back into the monorepo, solves a bug with tiny arrows, and improves TypeScript imports throughout the monorepo.