Building Project Comet: Details Matter
I’m an engineer on the team at Adobe that’s developing Project Comet, a new UX design and prototyping tool. We’re building it from scratch rather than using an existing codebase, so there’s been plenty to do just getting the basics implemented. Multithreaded rendering, vector rasterization, canvas decoration drawing and interaction handling…all before you can even draw a rectangle!
But for me, more than all the big-picture architecture questions and complex algorithms, the most challenging and satisfying work is in the little things: ironing out all the nuances and details of the user experience.
Sometimes you get it right the first time — the obvious design for a feature or an interaction turns out to just work. Other times, though, when you actually build it and try it out, one messy little detail just feels broken. That’s always frustrating when it happens, and it can be tempting to just sweep it under the rug. But when you keep working at it, and find a solution that makes the experience feel finely tuned and polished, the frustration can turn into delight.
Here are a few of my favorite examples.
Keeping It Real: Direct Manipulation
One of the headline features of Project Comet is Repeat Grid, a new tool that makes it easy to lay out repeated items in a design, like a contact list or a photo gallery.
With the Repeat Grid, you can draw an example item, then drag out the grid handles to add rows and columns fluidly. Each item can have its own content, but the layout of each item is kept consistent across the grid.
To adjust the overall grid layout, you can just mouse over the empty padding area between two rows or columns to highlight it, then click and drag to change the padding across the whole grid, as you can see at the end of the example. This mouseover affordance makes layout adjustments feel very direct and fluid.
However, when we built the initial prototype of this padding design and tried it out, we ran into an interesting problem.
The obvious way to handle the mouse drag is to change the padding by the number of pixels the mouse moves. This works fine if you drag the padding underneath the first row. But it starts to feel weird if you drag the padding elsewhere — say, between the fourth and fifth rows in a larger grid:
You can see that the padding doesn’t track the mouse — it seems to get bigger much faster than the mouse is moving, even though we’re adding just one pixel of padding for every pixel the mouse moves. This happens because the padding is the same between each row by default, so adding one pixel to the padding adds one pixel below every row. That means the next row below the mouse will actually move four times as fast as the mouse does. That destroys the illusion of direct manipulation; we really want the padding to stay “attached” to the mouse as the user drags it.
The solution turns out to be simple: just divide the mouse drag distance by the index of the row above the mouse. For example, when dragging the padding below the fourth row, we divide the drag distance by four, so dragging the mouse four pixels increases the padding between each row by one. This keeps the padding under the mouse in sync with the mouse position no matter which row’s padding you drag.
Because we also want to keep everything pixel-aligned, we only allow whole-number padding values, so in this case you actually have to drag the mouse four pixels before the padding changes. When we first discussed this we weren’t sure if it would feel right — we were worried it might make the mouse response feel too slow if you were dragging over a row or column far to the bottom or right. But once we tried it out in the prototype, it felt right, and we carried this heuristic over into the actual product.
Keeping It Consistent: Snapping and Resizing
The bread and butter of a design app is in its core layout tools: drawing, resizing, and aligning items. But even in these most basic of interactions — affordances that have been in design tools since the beginning of time (more or less) — we found subtle details that were surprisingly tricky to get right.
In Comet, as in many other design and layout tools, we automatically snap the edges of items to align with each other as you move or resize them. And as in other tools, users can hold down the Shift key while dragging a resize handle to maintain the aspect ratio (relative width and height) of the item. But after building the first versions of these two features, we found that they didn’t work well with each other.
The obvious implementation of edge snapping during a resize looks at the mouse position as you drag to see whether it lines up with the edge of a nearby shape. That works fine for ordinary resizes, as in the top example here.
When you do a Shift-resize, though, the mouse doesn’t always stay directly on the corner of the shape, because the corner has to be in a different location in order to preserve the shape’s proportions. You can see this in the bottom example, where the mouse drifts off the bottom of the square. Because of this, we can’t use the mouse position to figure out when to snap.
It would have been easy to write off this case, and in fact many of the other design tools we checked don’t seem to handle it — during a Shift-resize, they either continue to snap based on the mouse position, or they turn off edge snapping entirely. However, we felt it was important to deal with this case properly, because UX designers often work with images, and when resizing an image you almost always want to preserve its aspect ratio.
So we analyzed all the different combinations of resize constraints and snapping types to make the algorithm work predictably during constrained resizes. As shown below, it works well even in more advanced cases, like using Alt-Shift to resize an item from the center while maintaining its proportions, or Shift-dragging an edge handle.
Keeping It Smooth: Zooming and Artboards
Here’s a final example of a detail that was fun to get right. Like many other modern apps, when the user zooms in or out of the canvas, we use the GPU to make the zoom feel smooth. This involves a little bit of a cheat: the GPU is really good at redrawing bitmaps at different scales quickly, but in general it doesn’t help with redrawing vectors or text quickly at different scales. So during the zoom, we have the GPU just rescale the artwork on the canvas at its current resolution (which might briefly cause it to draw pixellated), then redraw it at the final resolution after the zoom is finished.
This caused a problem for artboard titles. If we were to zoom them along with the artwork, they would get temporarily bigger or smaller during the zoom, which would look really weird. When you’re zooming far in, you don’t want the titles to become huge and pixellated! It seemed like a small detail, but we felt it would compromise the zooming experience. So we decided to try to fix it.
As you can see in the example, we’re able to keep the artboard title sizes consistent during smooth zooms by decoupling the rendering of the artboard titles from the artwork. We implemented some tricks in the renderer architecture to keep the GPU’s rendering of the artwork in lockstep with the drawing of the artboard titles on each frame so they don’t drift relative to each other. This also made it possible for us to truncate the artboard titles when you’re zoomed out far enough that they would overlap otherwise. Though it’s not easy to see in an animated GIF, we’re still able to maintain a smooth frame rate during the zoom.
Make It Right or Let It Go?
When thinking about details like these, it can be difficult to know how far down the rabbit hole to go — are you taking the time to fix something that matters, or are you just deep-ending for no good reason? There are lots of factors you can consider: how many users will encounter it, whether it will make the feature hard to learn or use, whether it will block someone’s workflow. At the end of the day, though, there’s no hard and fast rule. And there always seems to be some other shiny new feature to work on that seems more urgent than ironing out details in an existing one.
We’ve tried to adopt the philosophy that if it’s a close call as to whether we should obsess about a detail, then we should obsess about it. Jared M. Spool once said:
“Good design, when it’s done well, becomes invisible. It’s only when it’s done poorly that we notice it.”
I think that’s true for engineering too.
This post was originally published on Medium.