summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-23 22:10:41 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-23 22:10:41 +0300
commit48c3553660f4c20fc3bb4fb5df95c058adf9dd87 (patch)
tree7429980e80d3569b96f4f8225213b45a64a778b1 /src/ui
parent1d4402e7ee9f208232227ded6add865b67e849af (diff)
ListWidget: Dragging items to reorder
Items can be marked as draggable, and additionally as drop targets.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/listwidget.c121
-rw-r--r--src/ui/listwidget.h3
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
34void init_ListItem(iListItem *d) { 34void 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
39void deinit_ListItem(iListItem *d) { 41void 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
257const iAnyObject *constDragItem_ListWidget(const iListWidget *d) {
258 return constItem_ListWidget(d, d->dragItem);
259}
260
251const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { 261const 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
324static 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
349static 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
314static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { 366static 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
44iDeclareObjectConstruction(ListItem) 46iDeclareObjectConstruction(ListItem)
@@ -75,6 +77,7 @@ int visCount_ListWidget (const iListWidget *);
75size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); 77size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos);
76iRect itemRect_ListWidget (const iListWidget *, size_t index); 78iRect itemRect_ListWidget (const iListWidget *, size_t index);
77const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); 79const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index);
80const iAnyObject * constDragItem_ListWidget (const iListWidget *);
78const iAnyObject * constHoverItem_ListWidget (const iListWidget *); 81const iAnyObject * constHoverItem_ListWidget (const iListWidget *);
79size_t hoverItemIndex_ListWidget (const iListWidget *); 82size_t hoverItemIndex_ListWidget (const iListWidget *);
80 83