summaryrefslogtreecommitdiff
path: root/src/ui/listwidget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/listwidget.c')
-rw-r--r--src/ui/listwidget.c155
1 files changed, 143 insertions, 12 deletions
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index d51516d1..ca15cc20 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}
@@ -267,7 +277,7 @@ size_t hoverItemIndex_ListWidget(const iListWidget *d) {
267 return d->hoverItem; 277 return d->hoverItem;
268} 278}
269 279
270static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { 280void setHoverItem_ListWidget(iListWidget *d, size_t index) {
271 if (index < size_PtrArray(&d->items)) { 281 if (index < size_PtrArray(&d->items)) {
272 const iListItem *item = at_PtrArray(&d->items, index); 282 const iListItem *item = at_PtrArray(&d->items, index);
273 if (item->isSeparator) { 283 if (item->isSeparator) {
@@ -284,7 +294,7 @@ static void setHoverItem_ListWidget_(iListWidget *d, size_t index) {
284 294
285void updateMouseHover_ListWidget(iListWidget *d) { 295void updateMouseHover_ListWidget(iListWidget *d) {
286 const iInt2 mouse = mouseCoord_Window(get_Window(), 0); 296 const iInt2 mouse = mouseCoord_Window(get_Window(), 0);
287 setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse)); 297 setHoverItem_ListWidget(d, itemIndex_ListWidget(d, mouse));
288} 298}
289 299
290void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) { 300void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) {
@@ -308,7 +318,51 @@ static void updateHover_ListWidget_(iListWidget *d, const iInt2 mouse) {
308 contains_Widget(constAs_Widget(d), mouse)) { 318 contains_Widget(constAs_Widget(d), mouse)) {
309 hover = itemIndex_ListWidget(d, mouse); 319 hover = itemIndex_ListWidget(d, mouse);
310 } 320 }
311 setHoverItem_ListWidget_(d, hover); 321 setHoverItem_ListWidget(d, hover);
322}
323
324static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dstPos, iBool *isOnto) {
325 size_t index = itemIndex_ListWidget(d, dstPos);
326 const iListItem *item = constItem_ListWidget(d, index);
327 if (!item) {
328 index = (dstPos.y < mid_Rect(bounds_Widget(constAs_Widget(d))).y ? 0 : (numItems_ListWidget(d) - 1));
329 item = constItem_ListWidget(d, index);
330 }
331 const iRect rect = itemRect_ListWidget(d, index);
332 const iRangei span = ySpan_Rect(rect);
333 if (item->isDropTarget) {
334 const int pad = size_Range(&span) / 3;
335 if (dstPos.y >= span.start + pad && dstPos.y < span.end - pad) {
336 *isOnto = iTrue;
337 return index;
338 }
339 }
340 if (dstPos.y - span.start > span.end - dstPos.y) {
341 index++;
342 }
343 index = iMin(index, numItems_ListWidget(d));
344 *isOnto = iFalse;
345 return index;
346}
347
348static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) {
349 if (d->dragItem == iInvalidPos) {
350 return iFalse;
351 }
352 stop_Anim(&d->scrollY.pos);
353 iBool isOnto;
354 const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto);
355 if (index != d->dragItem) {
356 if (isOnto) {
357 postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index);
358 }
359 else {
360 postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index);
361 }
362 }
363 invalidateItem_ListWidget(d, d->dragItem);
364 d->dragItem = iInvalidPos;
365 return iTrue;
312} 366}
313 367
314static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { 368static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
@@ -333,10 +387,35 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
333 d->noHoverWhileScrolling = iFalse; 387 d->noHoverWhileScrolling = iFalse;
334 } 388 }
335 if (ev->type == SDL_MOUSEMOTION) { 389 if (ev->type == SDL_MOUSEMOTION) {
336 if (ev->motion.which != SDL_TOUCH_MOUSEID) { 390 const iInt2 mousePos = init_I2(ev->motion.x, ev->motion.y);
337 d->noHoverWhileScrolling = iFalse; 391 if (ev->motion.state == 0 /* not dragging */) {
392 if (ev->motion.which != SDL_TOUCH_MOUSEID) {
393 d->noHoverWhileScrolling = iFalse;
394 }
395 updateHover_ListWidget_(d, mousePos);
396 }
397 else if (d->dragItem != iInvalidPos) {
398 /* Start scrolling if near the ends. */
399 const int zone = 2 * d->itemHeight;
400 const iRect bounds = bounds_Widget(w);
401 float scrollSpeed = 0.0f;
402 if (mousePos.y > bottom_Rect(bounds) - zone) {
403 scrollSpeed = (mousePos.y - bottom_Rect(bounds) + zone) / (float) zone;
404 }
405 else if (mousePos.y < top_Rect(bounds) + zone) {
406 scrollSpeed = -(top_Rect(bounds) + zone - mousePos.y) / (float) zone;
407 }
408 scrollSpeed = iClamp(scrollSpeed, -1.0f, 1.0f);
409 if (iAbs(scrollSpeed) < 0.001f) {
410 stop_Anim(&d->scrollY.pos);
411 refresh_Widget(d);
412 }
413 else {
414 setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d),
415 scrollSpeed * scrollSpeed * gap_UI * 400);
416 refreshWhileScrolling_ListWidget_(d);
417 }
338 } 418 }
339 updateHover_ListWidget_(d, init_I2(ev->motion.x, ev->motion.y));
340 } 419 }
341 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 420 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
342 int amount = -ev->wheel.y; 421 int amount = -ev->wheel.y;
@@ -359,12 +438,33 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
359 redrawHoverItem_ListWidget_(d); 438 redrawHoverItem_ListWidget_(d);
360 return iTrue; 439 return iTrue;
361 case aborted_ClickResult: 440 case aborted_ClickResult:
441// endDrag_ListWidget_(d, pos_Click(&d->click));
442 if (d->dragItem != iInvalidPos) {
443 stop_Anim(&d->scrollY.pos);
444 invalidateItem_ListWidget(d, d->dragItem);
445 d->dragItem = iInvalidPos;
446 }
362 redrawHoverItem_ListWidget_(d); 447 redrawHoverItem_ListWidget_(d);
363 break; 448 break;
449 case drag_ClickResult:
450 if (d->dragItem == iInvalidPos && length_I2(delta_Click(&d->click)) > gap_UI) {
451 const size_t over = itemIndex_ListWidget(d, d->click.startPos);
452 if (over != iInvalidPos &&
453 ((const iListItem *) item_ListWidget(d, over))->isDraggable) {
454 d->dragItem = over;
455 d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)),
456 d->click.startPos);
457 invalidateItem_ListWidget(d, d->dragItem);
458 }
459 }
460 return d->dragItem != iInvalidPos;
364 case finished_ClickResult: 461 case finished_ClickResult:
462 if (endDrag_ListWidget_(d, pos_Click(&d->click))) {
463 return iTrue;
464 }
365 redrawHoverItem_ListWidget_(d); 465 redrawHoverItem_ListWidget_(d);
366 if (contains_Rect(innerBounds_Widget(w), pos_Click(&d->click)) && 466 if (contains_Rect(itemRect_ListWidget(d, d->hoverItem), pos_Click(&d->click)) &&
367 d->hoverItem != iInvalidSize) { 467 d->hoverItem != iInvalidPos) {
368 postCommand_Widget(w, "list.clicked arg:%zu item:%p", 468 postCommand_Widget(w, "list.clicked arg:%zu item:%p",
369 d->hoverItem, constHoverItem_ListWidget(d)); 469 d->hoverItem, constHoverItem_ListWidget(d));
370 } 470 }
@@ -391,6 +491,7 @@ static void draw_ListWidget_(const iListWidget *d) {
391 const int scrollY = pos_SmoothScroll(&d->scrollY); 491 const int scrollY = pos_SmoothScroll(&d->scrollY);
392 iPaint p; 492 iPaint p;
393 init_Paint(&p); 493 init_Paint(&p);
494 drawLayerEffects_Widget(w);
394 drawBackground_Widget(w); 495 drawBackground_Widget(w);
395 alloc_VisBuf(d->visBuf, bounds.size, d->itemHeight); 496 alloc_VisBuf(d->visBuf, bounds.size, d->itemHeight);
396 /* Update invalid regions/items. */ { 497 /* Update invalid regions/items. */ {
@@ -433,7 +534,9 @@ static void draw_ListWidget_(const iListWidget *d) {
433 init_I2(d->visBuf->texSize.x, d->itemHeight) }; 534 init_I2(d->visBuf->texSize.x, d->itemHeight) };
434 beginTarget_Paint(&p, buf->texture); 535 beginTarget_Paint(&p, buf->texture);
435 fillRect_Paint(&p, itemRect, bg[i]); 536 fillRect_Paint(&p, itemRect, bg[i]);
436 class_ListItem(item)->draw(item, &p, itemRect, d); 537 if (index != d->dragItem) {
538 class_ListItem(item)->draw(item, &p, itemRect, d);
539 }
437 fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); 540 fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]);
438 } 541 }
439 } 542 }
@@ -447,7 +550,9 @@ static void draw_ListWidget_(const iListWidget *d) {
447 const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin), 550 const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin),
448 init_I2(d->visBuf->texSize.x, d->itemHeight) }; 551 init_I2(d->visBuf->texSize.x, d->itemHeight) };
449 fillRect_Paint(&p, itemRect, bg[i]); 552 fillRect_Paint(&p, itemRect, bg[i]);
450 class_ListItem(item)->draw(item, &p, itemRect, d); 553 if (j != d->dragItem) {
554 class_ListItem(item)->draw(item, &p, itemRect, d);
555 }
451 fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); 556 fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]);
452 } 557 }
453 } 558 }
@@ -458,6 +563,32 @@ static void draw_ListWidget_(const iListWidget *d) {
458 } 563 }
459 setClip_Paint(&p, bounds_Widget(w)); 564 setClip_Paint(&p, bounds_Widget(w));
460 draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); 565 draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds));
566 if (d->dragItem != iInvalidPos) {
567 const iInt2 mousePos = mouseCoord_Window(get_Window(), 0);
568 iInt2 pos = add_I2(mousePos, d->dragOrigin);
569 const iListItem *item = constAt_PtrArray(&d->items, d->dragItem);
570 const iRect itemRect = { init_I2(left_Rect(bounds), pos.y), init_I2(d->visBuf->texSize.x, d->itemHeight) };
571 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
572 iBool dstOnto;
573 const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto);
574 if (dstIndex != d->dragItem) {
575 const iRect dstRect = itemRect_ListWidget(d, dstIndex);
576 p.alpha = 0xff;
577 if (dstOnto) {
578 drawRectThickness_Paint(&p, dstRect, gap_UI / 2, uiTextAction_ColorId);
579 }
580 else if (dstIndex != d->dragItem + 1) {
581 fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4),
582 init_I2(width_Rect(dstRect), gap_UI / 2) },
583 uiTextAction_ColorId);
584 }
585 }
586 p.alpha = 0x80;
587 setOpacity_Text(0.5f);
588 class_ListItem(item)->draw(item, &p, itemRect, d);
589 setOpacity_Text(1.0f);
590 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
591 }
461 unsetClip_Paint(&p); 592 unsetClip_Paint(&p);
462 drawChildren_Widget(w); 593 drawChildren_Widget(w);
463} 594}