diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-09-23 22:10:41 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-09-23 22:10:41 +0300 |
commit | 48c3553660f4c20fc3bb4fb5df95c058adf9dd87 (patch) | |
tree | 7429980e80d3569b96f4f8225213b45a64a778b1 /src | |
parent | 1d4402e7ee9f208232227ded6add865b67e849af (diff) |
ListWidget: Dragging items to reorder
Items can be marked as draggable, and additionally as drop targets.
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/listwidget.c | 121 | ||||
-rw-r--r-- | src/ui/listwidget.h | 3 |
2 files changed, 116 insertions, 8 deletions
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index 6fa23986..6a1372ab 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -32,8 +32,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | #include <the_Foundation/intset.h> | 32 | #include <the_Foundation/intset.h> |
33 | 33 | ||
34 | void init_ListItem(iListItem *d) { | 34 | void init_ListItem(iListItem *d) { |
35 | d->isSeparator = iFalse; | 35 | d->isSeparator = iFalse; |
36 | d->isSelected = iFalse; | 36 | d->isSelected = iFalse; |
37 | d->isDraggable = iFalse; | ||
38 | d->isDropTarget = iFalse; | ||
37 | } | 39 | } |
38 | 40 | ||
39 | void deinit_ListItem(iListItem *d) { | 41 | void deinit_ListItem(iListItem *d) { |
@@ -54,6 +56,8 @@ struct Impl_ListWidget { | |||
54 | int itemHeight; | 56 | int itemHeight; |
55 | iPtrArray items; | 57 | iPtrArray items; |
56 | size_t hoverItem; | 58 | size_t hoverItem; |
59 | size_t dragItem; | ||
60 | iInt2 dragOrigin; /* offset from mouse to drag item's top-left corner */ | ||
57 | iClick click; | 61 | iClick click; |
58 | iIntSet invalidItems; | 62 | iIntSet invalidItems; |
59 | iVisBuf *visBuf; | 63 | iVisBuf *visBuf; |
@@ -95,6 +99,8 @@ void init_ListWidget(iListWidget *d) { | |||
95 | d->noHoverWhileScrolling = iFalse; | 99 | d->noHoverWhileScrolling = iFalse; |
96 | init_PtrArray(&d->items); | 100 | init_PtrArray(&d->items); |
97 | d->hoverItem = iInvalidPos; | 101 | d->hoverItem = iInvalidPos; |
102 | d->dragItem = iInvalidPos; | ||
103 | d->dragOrigin = zero_I2(); | ||
98 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 104 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
99 | init_IntSet(&d->invalidItems); | 105 | init_IntSet(&d->invalidItems); |
100 | d->visBuf = new_VisBuf(); | 106 | d->visBuf = new_VisBuf(); |
@@ -248,6 +254,10 @@ const iAnyObject *constItem_ListWidget(const iListWidget *d, size_t index) { | |||
248 | return NULL; | 254 | return NULL; |
249 | } | 255 | } |
250 | 256 | ||
257 | const iAnyObject *constDragItem_ListWidget(const iListWidget *d) { | ||
258 | return constItem_ListWidget(d, d->dragItem); | ||
259 | } | ||
260 | |||
251 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { | 261 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { |
252 | return constItem_ListWidget(d, d->hoverItem); | 262 | return constItem_ListWidget(d, d->hoverItem); |
253 | } | 263 | } |
@@ -311,6 +321,48 @@ static void updateHover_ListWidget_(iListWidget *d, const iInt2 mouse) { | |||
311 | setHoverItem_ListWidget_(d, hover); | 321 | setHoverItem_ListWidget_(d, hover); |
312 | } | 322 | } |
313 | 323 | ||
324 | static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dstPos, iBool *isOnto) { | ||
325 | size_t index = itemIndex_ListWidget(d, dstPos); | ||
326 | const iRect rect = itemRect_ListWidget(d, index); | ||
327 | const iListItem *item = constItem_ListWidget(d, index); | ||
328 | const iRangei span = ySpan_Rect(rect); | ||
329 | if (!item) { | ||
330 | item = constItem_ListWidget( | ||
331 | d, | ||
332 | dstPos.y < mid_Rect(bounds_Widget(constAs_Widget(d))).y ? 0 : (numItems_ListWidget(d) - 1)); | ||
333 | } | ||
334 | if (item->isDropTarget) { | ||
335 | const int pad = size_Range(&span) / 4; | ||
336 | if (dstPos.y >= span.start + pad && dstPos.y < span.end) { | ||
337 | *isOnto = iTrue; | ||
338 | return index; | ||
339 | } | ||
340 | } | ||
341 | if (dstPos.y - span.start > span.end - dstPos.y) { | ||
342 | index++; | ||
343 | } | ||
344 | index = iMin(index, numItems_ListWidget(d)); | ||
345 | *isOnto = iFalse; | ||
346 | return index; | ||
347 | } | ||
348 | |||
349 | static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) { | ||
350 | if (d->dragItem == iInvalidPos) { | ||
351 | return iFalse; | ||
352 | } | ||
353 | iBool isOnto; | ||
354 | const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto); | ||
355 | if (isOnto) { | ||
356 | postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index); | ||
357 | } | ||
358 | else if (index != d->dragItem) { | ||
359 | postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index); | ||
360 | } | ||
361 | invalidateItem_ListWidget(d, d->dragItem); | ||
362 | d->dragItem = iInvalidPos; | ||
363 | return iTrue; | ||
364 | } | ||
365 | |||
314 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | 366 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { |
315 | iWidget *w = as_Widget(d); | 367 | iWidget *w = as_Widget(d); |
316 | if (isMetricsChange_UserEvent(ev)) { | 368 | if (isMetricsChange_UserEvent(ev)) { |
@@ -333,10 +385,15 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
333 | d->noHoverWhileScrolling = iFalse; | 385 | d->noHoverWhileScrolling = iFalse; |
334 | } | 386 | } |
335 | if (ev->type == SDL_MOUSEMOTION) { | 387 | if (ev->type == SDL_MOUSEMOTION) { |
336 | if (ev->motion.which != SDL_TOUCH_MOUSEID) { | 388 | if (ev->motion.state == 0 /* not dragging */) { |
337 | d->noHoverWhileScrolling = iFalse; | 389 | if (ev->motion.which != SDL_TOUCH_MOUSEID) { |
390 | d->noHoverWhileScrolling = iFalse; | ||
391 | } | ||
392 | updateHover_ListWidget_(d, init_I2(ev->motion.x, ev->motion.y)); | ||
393 | } | ||
394 | else if (d->dragItem != iInvalidPos) { | ||
395 | refresh_Widget(d); | ||
338 | } | 396 | } |
339 | updateHover_ListWidget_(d, init_I2(ev->motion.x, ev->motion.y)); | ||
340 | } | 397 | } |
341 | if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { | 398 | if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { |
342 | int amount = -ev->wheel.y; | 399 | int amount = -ev->wheel.y; |
@@ -359,12 +416,28 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
359 | redrawHoverItem_ListWidget_(d); | 416 | redrawHoverItem_ListWidget_(d); |
360 | return iTrue; | 417 | return iTrue; |
361 | case aborted_ClickResult: | 418 | case aborted_ClickResult: |
419 | endDrag_ListWidget_(d, pos_Click(&d->click)); | ||
362 | redrawHoverItem_ListWidget_(d); | 420 | redrawHoverItem_ListWidget_(d); |
363 | break; | 421 | break; |
422 | case drag_ClickResult: | ||
423 | if (d->dragItem == iInvalidPos && length_I2(delta_Click(&d->click)) > gap_UI) { | ||
424 | const size_t over = itemIndex_ListWidget(d, d->click.startPos); | ||
425 | if (over != iInvalidPos && | ||
426 | ((const iListItem *) item_ListWidget(d, over))->isDraggable) { | ||
427 | d->dragItem = over; | ||
428 | d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)), | ||
429 | pos_Click(&d->click)); | ||
430 | invalidateItem_ListWidget(d, d->dragItem); | ||
431 | } | ||
432 | } | ||
433 | return d->dragItem != iInvalidPos; | ||
364 | case finished_ClickResult: | 434 | case finished_ClickResult: |
435 | if (endDrag_ListWidget_(d, pos_Click(&d->click))) { | ||
436 | return iTrue; | ||
437 | } | ||
365 | redrawHoverItem_ListWidget_(d); | 438 | redrawHoverItem_ListWidget_(d); |
366 | if (contains_Rect(innerBounds_Widget(w), pos_Click(&d->click)) && | 439 | if (contains_Rect(innerBounds_Widget(w), pos_Click(&d->click)) && |
367 | d->hoverItem != iInvalidSize) { | 440 | d->hoverItem != iInvalidPos) { |
368 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", | 441 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", |
369 | d->hoverItem, constHoverItem_ListWidget(d)); | 442 | d->hoverItem, constHoverItem_ListWidget(d)); |
370 | } | 443 | } |
@@ -434,7 +507,9 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
434 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; | 507 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; |
435 | beginTarget_Paint(&p, buf->texture); | 508 | beginTarget_Paint(&p, buf->texture); |
436 | fillRect_Paint(&p, itemRect, bg[i]); | 509 | fillRect_Paint(&p, itemRect, bg[i]); |
437 | class_ListItem(item)->draw(item, &p, itemRect, d); | 510 | if (index != d->dragItem) { |
511 | class_ListItem(item)->draw(item, &p, itemRect, d); | ||
512 | } | ||
438 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); | 513 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); |
439 | } | 514 | } |
440 | } | 515 | } |
@@ -448,7 +523,9 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
448 | const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin), | 523 | const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin), |
449 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; | 524 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; |
450 | fillRect_Paint(&p, itemRect, bg[i]); | 525 | fillRect_Paint(&p, itemRect, bg[i]); |
451 | class_ListItem(item)->draw(item, &p, itemRect, d); | 526 | if (j != d->dragItem) { |
527 | class_ListItem(item)->draw(item, &p, itemRect, d); | ||
528 | } | ||
452 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); | 529 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); |
453 | } | 530 | } |
454 | } | 531 | } |
@@ -459,6 +536,34 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
459 | } | 536 | } |
460 | setClip_Paint(&p, bounds_Widget(w)); | 537 | setClip_Paint(&p, bounds_Widget(w)); |
461 | draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); | 538 | draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); |
539 | if (d->dragItem != iInvalidPos) { | ||
540 | const iInt2 mousePos = mouseCoord_Window(get_Window(), 0); | ||
541 | iInt2 pos = add_I2(mousePos, d->dragOrigin); | ||
542 | const iListItem *item = constAt_PtrArray(&d->items, d->dragItem); | ||
543 | const iRect itemRect = { init_I2(left_Rect(bounds), pos.y), init_I2(d->visBuf->texSize.x, d->itemHeight) }; | ||
544 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
545 | // setOpacity_Text(0.25f); | ||
546 | // SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
547 | // setOpacity_Text(1.0f); | ||
548 | iBool dstOnto; | ||
549 | const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto); | ||
550 | if (dstIndex != d->dragItem && dstIndex != d->dragItem + 1) { | ||
551 | const iRect dstRect = itemRect_ListWidget(d, dstIndex); | ||
552 | // SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
553 | p.alpha = 0xff; | ||
554 | if (dstOnto) { | ||
555 | fillRect_Paint(&p, dstRect, uiTextAction_ColorId); | ||
556 | } | ||
557 | else { | ||
558 | fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4), | ||
559 | init_I2(width_Rect(dstRect), gap_UI / 2) }, | ||
560 | uiTextAction_ColorId); | ||
561 | } | ||
562 | } | ||
563 | p.alpha = 0x80; | ||
564 | class_ListItem(item)->draw(item, &p, itemRect, d); | ||
565 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
566 | } | ||
462 | unsetClip_Paint(&p); | 567 | unsetClip_Paint(&p); |
463 | drawChildren_Widget(w); | 568 | drawChildren_Widget(w); |
464 | } | 569 | } |
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h index 314c183a..e586a003 100644 --- a/src/ui/listwidget.h +++ b/src/ui/listwidget.h | |||
@@ -39,6 +39,8 @@ struct Impl_ListItem { | |||
39 | iObject object; | 39 | iObject object; |
40 | iBool isSeparator; | 40 | iBool isSeparator; |
41 | iBool isSelected; | 41 | iBool isSelected; |
42 | iBool isDraggable; | ||
43 | iBool isDropTarget; /* may drag-and-drop another item on this */ | ||
42 | }; | 44 | }; |
43 | 45 | ||
44 | iDeclareObjectConstruction(ListItem) | 46 | iDeclareObjectConstruction(ListItem) |
@@ -75,6 +77,7 @@ int visCount_ListWidget (const iListWidget *); | |||
75 | size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); | 77 | size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); |
76 | iRect itemRect_ListWidget (const iListWidget *, size_t index); | 78 | iRect itemRect_ListWidget (const iListWidget *, size_t index); |
77 | const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); | 79 | const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); |
80 | const iAnyObject * constDragItem_ListWidget (const iListWidget *); | ||
78 | const iAnyObject * constHoverItem_ListWidget (const iListWidget *); | 81 | const iAnyObject * constHoverItem_ListWidget (const iListWidget *); |
79 | size_t hoverItemIndex_ListWidget (const iListWidget *); | 82 | size_t hoverItemIndex_ListWidget (const iListWidget *); |
80 | 83 | ||