openscopeproject / TrguiNG

Remote GUI for Transmission torrent daemon
GNU Affero General Public License v3.0
278 stars 33 forks source link

Select rows by dragging #128

Open jpovixwm opened 7 months ago

jpovixwm commented 7 months ago

This feature comes in handy when you need to perform an action on multiple rows but don't have a keyboard around. Using only the mouse, I don't think it's currently possible to select more than a single row, because that requires the use of the Ctrl or Shift keys. Drag to select is present in transmission-qt and qBittorrent.

I am going to experiment a bit with how this could potentially be implemented, but I can't provide any time estimates, so it's still up for grabs for anyone else willing to give it a go.

qu1ck commented 7 months ago

This would likely conflict with scrolling gesture on touchscreens. If there is a way to implement it without breaking scroll then I'm not opposed to it.

jpovixwm commented 7 months ago

It looks like we can check event.target.hasPointerCapture() inside of a pointerdown event handler in order to disable this feature on touchscreens. I've pushed a rudimentary POC branch to my fork where I use this approach. I've tested it on Windows in Tauri, Brave and Firefox using a mouse - drag-to-select works, and on Android in Brave and Firefox:

In principle, this works due to "Implicit pointer capture", as described in the Pointer Events spec: https://w3c.github.io/pointerevents/#dfn-implicit-pointer-capture

Can you check if it behaves correctly on your touch enabled devices?

Other than that, there's also the question of how to handle row reordering during drag-to-select. This could be tricky if the table is sorted by highly dynamic columns, such as UL/DL speeds. If a row gets selected during drag-to-select, but is then reordered somewhere further away from the implicit "selection rectangle" defined by initial and current pointer positions, should the row stay selected, or should we only select rows contained within this rectangle? I think the latter makes more sense, even if the rectangle is never drawn.

Or should the rectangle be drawn, as a form of feedback to the user? Transmission-qt and qBittorrent don't draw the rectangle.

qu1ck commented 7 months ago

I don't have any non android touch devices so my testing will not expand the coverage. I'm mostly curious about windows touch laptops and the like.

If a row gets selected during drag-to-select, but is then reordered somewhere further away from the implicit "selection rectangle" defined by initial and current pointer positions, should the row stay selected, or should we only select rows contained within this rectangle? I think the latter makes more sense, even if the rectangle is never drawn.

Yes, latter makes sense, it would be consistent with shift clicks.

Drawing a rectangle is relevant for selecting objects on 2d plane, we only have one dimension here so highlighted rows give enough feedback.

qu1ck commented 7 months ago

I think we can make it an opt-in feature with a setting.

jpovixwm commented 7 months ago

I guess that could work as a last resort, though I'm not a big fan of this solution, because when using a mouse, this feature will always be useful (can't think of a scenario where it isn't) and when using a touchscreen it will likely never be useful (unless we come up with some kludge solution involving long-tapping to initiate the drag-to-select behavior or whatever, which would then conflict with the context menu which is also triggered by a long tap). In this case I think it would be better to just check event.pointerType and only enable the behavior for events explicitly generated by a mouse. I initially went with event.target.hasPointerCapture() because I think it provides a more universal way of telling whether the user can or cannot scroll the list by moving the pointer after pointerdown is fired. (Which now makes me wonder how does it behave if the list doesn't have enough entries for a scrollbar to appear, is the implicit pointer capture still carried out then?)

Anyway, I'm currently a bit stuck trying to find a sane approach to scrolling the list automatically when the pointer device comes near the edges of the container. This is implemented internally in react-beautiful-dnd, so I think the implementation for drag-to-select should match that to make the UX consistent. But their implementation is far from simple: https://github.com/atlassian/react-beautiful-dnd/tree/v13.1.1/src/state/auto-scroller and I'd really prefer to avoid having to re-implement that just for a seemingly basic feature of auto-scrolling a container.

qu1ck commented 7 months ago

Drag to select is not really something I've seen used in UI for lists. I've seen it in tables where cells are selectable, but those are 2D, it makes sense there. It feels weird to me but I accept that others may be used to it.

Scrolling behavior would likely be a pain to implement. In fact it's likely generally impossible to implement ergonomically with scrolling speed varying by how much you extend the pointer out of the container. If you get past the window border you won't get any events at all.

I'm not sure it's worth putting a lot of effort into auto scrolling.

jpovixwm commented 4 months ago

I revisited this problem today, and it occurred to me that keyboard-less multi selection could be facilitated via a dedicated column:

The way I see it, it would work like this:

In general, this should be far easier to implement than drag-to-select and would likely work better on mobile devices/touch screens. Before I put any serious effort into it, I'd like to ask for your opinion on this approach.

qu1ck commented 4 months ago

This looks like a better idea, assuming it can be made intuitive. Also I'm not sold on checkmark look maybe just a filled blue circle like a radio button.

Clicking or tapping the circle would select/deselect the row

Like ctrl-click, right? I.e. not affecting selected status of other rows.

"Long clicking" or tapping and holding for ~500 ms would do range selection

For this to be intuitive it needs visual feedback, for example the circle gradually filling while you are holding mouse button. That will give user the hint that it's different from a simple click.

Optionally, the column's header could also be a clickable circle

I like this too (although needs a bit more work to have custom header cell rendering).

jpovixwm commented 4 months ago

Like ctrl-click, right? I.e. not affecting selected status of other rows.

Yes, like Ctrl-click.

I'll play with it when I get the time to see if I can come up with something that looks and feels good. I wonder if it would be better to have the entire cell's rectangular area act as the selection button, though, so that you don't have to precisely aim for the smallish circle. With a mouse it's not that bad, but with a touch screen it could be annoying if you missed the circle and your meticulously crafted multi-selection got replaced with the tapped row in an instant 😂

qu1ck commented 4 months ago

You will be limited by row height anyway, it should be at least 4-5em for comfortable touch area but that is too sparse for desktop UI. But for the hypothetical future mobile mode of the table where the torrrents table is replaced with a list of thick rows with most useful fields packed into multi line element, it would work better.

jpovixwm commented 4 months ago

I ended up with this look and feel for now:

What do you think?

qu1ck commented 4 months ago

Looks good.

jpovixwm commented 4 months ago

Unfortunately, due to a bug in Mantine's Transition component, this will need to wait until TrguiNG is updated to Mantine 7.

qu1ck commented 4 months ago

Mantine 7 changed the styling system and has other breaking changes. I'm not sure when I'll get around to updating to v7.

jpovixwm commented 4 months ago

I managed to find a workaround for that bug, seems to work fine in Win 10 Edge webview (Tauri) - other browsers not tested. I've pushed a branch in my fork, feel free to take a look and provide feedback. The implementation is still not complete though:

There's also a small issue I've found with the multi-selection behavior, but it may be out of scope here because it can also be reproduced with the existing keyboard-based multi-select:

  1. Start up TrguiNG fresh, so you don't have any existing selection.
  2. Ctrl+shift+click some row.
  3. Ctrl+shift+click another row (at least one row apart from the first one)
  4. -> The rows in-between are not selected, but I think they should be selected because shift+ctrl+click is supposed to select all rows between the clicked row and the most recently selected row.