diff options
Diffstat (limited to 'src/ui/listwidget.c')
-rw-r--r-- | src/ui/listwidget.c | 155 |
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 | ||
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 | } |
@@ -267,7 +277,7 @@ size_t hoverItemIndex_ListWidget(const iListWidget *d) { | |||
267 | return d->hoverItem; | 277 | return d->hoverItem; |
268 | } | 278 | } |
269 | 279 | ||
270 | static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { | 280 | void 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 | ||
285 | void updateMouseHover_ListWidget(iListWidget *d) { | 295 | void 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 | ||
290 | void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) { | 300 | void 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 | |||
324 | static 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 | |||
348 | static 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 | ||
314 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | 368 | static 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 | } |