From 446df26adb969dcb4dd9cf5e171fbb9453a8e27c Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Fri, 24 Sep 2021 07:36:08 +0300 Subject: SidebarWidget: Reordering bookmarks manually One can now drag and drop bookmarks to change their order in the list. --- src/bookmarks.c | 14 +++++++++++++ src/bookmarks.h | 10 +++++++--- src/ui/listwidget.c | 53 +++++++++++++++++++++++++++++++++++--------------- src/ui/listwidget.h | 1 + src/ui/sidebarwidget.c | 31 +++++++++++++++++++++++++++++ src/ui/util.c | 2 +- 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/src/bookmarks.c b/src/bookmarks.c index 94f4be4e..616e4632 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c @@ -402,6 +402,20 @@ iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) { return (iBookmark *) value_Hash(&d->bookmarks, id); } +void reorder_Bookmarks(iBookmarks *d, uint32_t id, int newOrder) { + lock_Mutex(d->mtx); + iForEach(Hash, i, &d->bookmarks) { + iBookmark *bm = (iBookmark *) i.value; + if (id_Bookmark(bm) == id) { + bm->order = newOrder; + } + else if (bm->order >= newOrder) { + bm->order++; + } + } + unlock_Mutex(d->mtx); +} + iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { iRegExpMatch m; init_RegExpMatch(&m); diff --git a/src/bookmarks.h b/src/bookmarks.h index dc7eca9a..0de930d7 100644 --- a/src/bookmarks.h +++ b/src/bookmarks.h @@ -32,6 +32,8 @@ iDeclareType(GmRequest) iDeclareType(Bookmark) iDeclareTypeConstruction(Bookmark) +/* TODO: Make the special internal tags a bitfield, separate from user's tags. */ + #define headings_BookmarkTag "headings" #define homepage_BookmarkTag "homepage" #define linkSplit_BookmarkTag "linksplit" @@ -78,16 +80,18 @@ iDeclareTypeConstruction(Bookmarks) void clear_Bookmarks (iBookmarks *); void load_Bookmarks (iBookmarks *, const char *dirPath); +void save_Bookmarks (const iBookmarks *, const char *dirPath); + uint32_t add_Bookmarks (iBookmarks *, const iString *url, const iString *title, const iString *tags, iChar icon); iBool remove_Bookmarks (iBookmarks *, uint32_t id); iBookmark * get_Bookmarks (iBookmarks *, uint32_t id); +void reorder_Bookmarks (iBookmarks *, uint32_t id, int newOrder); +iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon); void fetchRemote_Bookmarks (iBookmarks *); void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req); -iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon); -iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url); -void save_Bookmarks (const iBookmarks *, const char *dirPath); +iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url); uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *); diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index 6a1372ab..a34f3d03 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c @@ -277,7 +277,7 @@ size_t hoverItemIndex_ListWidget(const iListWidget *d) { return d->hoverItem; } -static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { +void setHoverItem_ListWidget(iListWidget *d, size_t index) { if (index < size_PtrArray(&d->items)) { const iListItem *item = at_PtrArray(&d->items, index); if (item->isSeparator) { @@ -294,7 +294,7 @@ static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { void updateMouseHover_ListWidget(iListWidget *d) { const iInt2 mouse = mouseCoord_Window(get_Window(), 0); - setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse)); + setHoverItem_ListWidget(d, itemIndex_ListWidget(d, mouse)); } void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) { @@ -318,19 +318,18 @@ static void updateHover_ListWidget_(iListWidget *d, const iInt2 mouse) { contains_Widget(constAs_Widget(d), mouse)) { hover = itemIndex_ListWidget(d, mouse); } - setHoverItem_ListWidget_(d, hover); + setHoverItem_ListWidget(d, hover); } static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dstPos, iBool *isOnto) { size_t index = itemIndex_ListWidget(d, dstPos); - const iRect rect = itemRect_ListWidget(d, index); const iListItem *item = constItem_ListWidget(d, index); - const iRangei span = ySpan_Rect(rect); if (!item) { - item = constItem_ListWidget( - d, - dstPos.y < mid_Rect(bounds_Widget(constAs_Widget(d))).y ? 0 : (numItems_ListWidget(d) - 1)); + index = (dstPos.y < mid_Rect(bounds_Widget(constAs_Widget(d))).y ? 0 : (numItems_ListWidget(d) - 1)); + item = constItem_ListWidget(d, index); } + const iRect rect = itemRect_ListWidget(d, index); + const iRangei span = ySpan_Rect(rect); if (item->isDropTarget) { const int pad = size_Range(&span) / 4; if (dstPos.y >= span.start + pad && dstPos.y < span.end) { @@ -350,6 +349,7 @@ static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) { if (d->dragItem == iInvalidPos) { return iFalse; } + stop_Anim(&d->scrollY.pos); iBool isOnto; const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto); if (isOnto) { @@ -385,14 +385,34 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { d->noHoverWhileScrolling = iFalse; } if (ev->type == SDL_MOUSEMOTION) { + const iInt2 mousePos = init_I2(ev->motion.x, ev->motion.y); if (ev->motion.state == 0 /* not dragging */) { if (ev->motion.which != SDL_TOUCH_MOUSEID) { d->noHoverWhileScrolling = iFalse; } - updateHover_ListWidget_(d, init_I2(ev->motion.x, ev->motion.y)); + updateHover_ListWidget_(d, mousePos); } else if (d->dragItem != iInvalidPos) { - refresh_Widget(d); + /* Start scrolling if near the ends. */ + const int zone = d->itemHeight; + const iRect bounds = bounds_Widget(w); + float scrollSpeed = 0.0f; + if (mousePos.y > bottom_Rect(bounds) - zone) { + scrollSpeed = (mousePos.y - bottom_Rect(bounds) + zone) / (float) zone; + } + else if (mousePos.y < top_Rect(bounds) + zone) { + scrollSpeed = -(top_Rect(bounds) + zone - mousePos.y) / (float) zone; + } + scrollSpeed = iClamp(scrollSpeed, -1.0f, 1.0f); + if (iAbs(scrollSpeed) < 0.001f) { + stop_Anim(&d->scrollY.pos); + refresh_Widget(d); + } + else { + setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d), + iAbs(scrollSpeed * gap_UI * 100)); + refreshWhileScrolling_ListWidget_(d); + } } } if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { @@ -416,7 +436,12 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { redrawHoverItem_ListWidget_(d); return iTrue; case aborted_ClickResult: - endDrag_ListWidget_(d, pos_Click(&d->click)); +// endDrag_ListWidget_(d, pos_Click(&d->click)); + if (d->dragItem != iInvalidPos) { + stop_Anim(&d->scrollY.pos); + invalidateItem_ListWidget(d, d->dragItem); + d->dragItem = iInvalidPos; + } redrawHoverItem_ListWidget_(d); break; case drag_ClickResult: @@ -436,7 +461,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { return iTrue; } redrawHoverItem_ListWidget_(d); - if (contains_Rect(innerBounds_Widget(w), pos_Click(&d->click)) && + if (contains_Rect(itemRect_ListWidget(d, d->hoverItem), pos_Click(&d->click)) && d->hoverItem != iInvalidPos) { postCommand_Widget(w, "list.clicked arg:%zu item:%p", d->hoverItem, constHoverItem_ListWidget(d)); @@ -542,14 +567,10 @@ static void draw_ListWidget_(const iListWidget *d) { const iListItem *item = constAt_PtrArray(&d->items, d->dragItem); const iRect itemRect = { init_I2(left_Rect(bounds), pos.y), init_I2(d->visBuf->texSize.x, d->itemHeight) }; SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); -// setOpacity_Text(0.25f); -// SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); -// setOpacity_Text(1.0f); iBool dstOnto; const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto); if (dstIndex != d->dragItem && dstIndex != d->dragItem + 1) { const iRect dstRect = itemRect_ListWidget(d, dstIndex); -// SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); p.alpha = 0xff; if (dstOnto) { fillRect_Paint(&p, dstRect, uiTextAction_ColorId); diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h index e586a003..dfa24c07 100644 --- a/src/ui/listwidget.h +++ b/src/ui/listwidget.h @@ -66,6 +66,7 @@ void scrollToItem_ListWidget (iListWidget *, size_t index); void scrollOffset_ListWidget (iListWidget *, int offset); void updateVisible_ListWidget (iListWidget *); void updateMouseHover_ListWidget (iListWidget *); +void setHoverItem_ListWidget (iListWidget *, size_t index); void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2)); diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 3463f1a5..20e43153 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c @@ -332,6 +332,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { iRegExp *homeTag = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", caseSensitive_RegExpOption)); iRegExp *subTag = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption)); iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); + iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption)); iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark_, NULL, NULL)) { const iBookmark *bm = i.ptr; @@ -352,6 +353,10 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { appendChar_String(&item->meta, 0x1f3e0); } init_RegExpMatch(&m); + if (matchString_RegExp(remoteTag, &bm->tags, &m)) { + item->listItem.isDraggable = iFalse; + } + init_RegExpMatch(&m); if (matchString_RegExp(remoteSourceTag, &bm->tags, &m)) { appendChar_String(&item->meta, 0x2913); item->isBold = iTrue; @@ -555,6 +560,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { #endif arrange_Widget(d->actions); arrange_Widget(as_Widget(d)); + updateMouseHover_ListWidget(d->list); } static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { @@ -1000,6 +1006,19 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * return iFalse; } +static void bookmarkMoved_SidebarWidget_(iSidebarWidget *d, size_t index, size_t beforeIndex) { + const iSidebarItem *movingItem = item_ListWidget(d->list, index); + const iBool isLast = (beforeIndex == numItems_ListWidget(d->list)); + const iSidebarItem *dstItem = item_ListWidget(d->list, + isLast ? numItems_ListWidget(d->list) - 1 + : beforeIndex); + const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id); + reorder_Bookmarks(bookmarks_App(), movingItem->id, dst->order + (isLast ? 1 : 0)); + updateItems_SidebarWidget_(d); + /* Don't confuse the user: keep the dragged item in hover state. */ + setHoverItem_ListWidget(d->list, index < beforeIndex ? beforeIndex - 1 : beforeIndex); +} + static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { iWidget *w = as_Widget(d); /* Handle commands. */ @@ -1107,6 +1126,18 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); return iTrue; } + else if (isCommand_Widget(w, ev, "list.dragged")) { + iAssert(d->mode == bookmarks_SidebarMode); + if (hasLabel_Command(cmd, "before")) { + bookmarkMoved_SidebarWidget_(d, + argU32Label_Command(cmd, "arg"), + argU32Label_Command(cmd, "before")); + } + else { + /* Dragged onto a folder. */ + } + return iTrue; + } else if (isCommand_Widget(w, ev, "menu.closed")) { // invalidateItem_ListWidget(d->list, d->contextIndex); } diff --git a/src/ui/util.c b/src/ui/util.c index 2b6ff929..7fa5d675 100644 --- a/src/ui/util.c +++ b/src/ui/util.c @@ -337,7 +337,7 @@ void setValue_Anim(iAnim *d, float to, uint32_t span) { } void setValueSpeed_Anim(iAnim *d, float to, float unitsPerSecond) { - if (iAbs(d->to - to) > 0.0001f) { + if (iAbs(d->to - to) > 0.0001f || !isFinished_Anim(d)) { const uint32_t now = SDL_GetTicks(); const float from = valueAt_Anim_(d, now); const float delta = to - from; -- cgit v1.2.3