diff --git a/src/pc/djui/djui_cursor.c b/src/pc/djui/djui_cursor.c index 7c336a74f..5d2470097 100644 --- a/src/pc/djui/djui_cursor.c +++ b/src/pc/djui/djui_cursor.c @@ -1,5 +1,6 @@ #include "djui.h" #include "djui_panel.h" +#include "djui_flow_layout.h" #include "pc/controller/controller_mouse.h" #include "pc/gfx/gfx_window_manager_api.h" #include "pc/pc_main.h" @@ -115,6 +116,24 @@ void djui_cursor_move(s8 xDir, s8 yDir) { if (pick != NULL) { sCursorMouseControlled = false; djui_cursor_input_controlled_center(pick); + + // auto-scroll scrollable flow layout to show the picked element + if (gDjuiFlowLayoutScrollRender) { + struct DjuiBase* parent = pick->parent; + while (parent) { + if (parent->render == gDjuiFlowLayoutScrollRender) { + struct DjuiFlowLayout* layout = (struct DjuiFlowLayout*)parent; + f32 targetTop = pick->elem.y; + f32 targetBot = pick->elem.y + pick->elem.height; + f32 visTop = parent->clip.y; + f32 visBot = parent->clip.y + parent->clip.height; + if (targetTop < visTop) { layout->scrollY -= (visTop - targetTop); } + else if (targetBot > visBot) { layout->scrollY += (targetBot - visBot); } + break; + } + parent = parent->parent; + } + } } } diff --git a/src/pc/djui/djui_flow_layout.c b/src/pc/djui/djui_flow_layout.c index da5111f26..9b686dfdb 100644 --- a/src/pc/djui/djui_flow_layout.c +++ b/src/pc/djui/djui_flow_layout.c @@ -1,5 +1,7 @@ #include "djui.h" +bool (*gDjuiFlowLayoutScrollRender)(struct DjuiBase*) = NULL; + //////////////// // properties // //////////////// @@ -41,6 +43,97 @@ static void djui_flow_layout_on_child_render(struct DjuiBase* base, struct DjuiB } } + //////////// + // scroll // +//////////// + +static bool djui_flow_layout_scroll_render(struct DjuiBase* base) { + struct DjuiFlowLayout* layout = (struct DjuiFlowLayout*)base; + + // draw background + djui_rect_render(base); + + // compute content height from visible children + f32 contentHeight = layout->margin.value; + struct DjuiBaseChild* child = base->child; + while (child != NULL) { + if (child->base->visible) { + contentHeight += child->base->height.value + layout->margin.value; + } + child = child->next; + } + layout->contentHeight = contentHeight; + + // clamp scroll + f32 maxScroll = contentHeight - base->elem.height - 32; + if (maxScroll < 0) { maxScroll = 0; } + if (layout->scrollY < 0) { layout->scrollY = 0; } + if (layout->scrollY > maxScroll) { layout->scrollY = maxScroll; } + + // shift comp so children render scrolled + base->comp.y -= layout->scrollY; + + // draw scrollbar indicator + if (maxScroll > 0) { + struct DjuiBaseRect* clip = &base->clip; + f32 thumbRatio = clip->height / contentHeight; + f32 thumbH = clip->height * thumbRatio; + if (thumbH < 20) { thumbH = 20; } + f32 thumbY = clip->y + (layout->scrollY / maxScroll) * (clip->height - thumbH); + f32 thumbX = clip->x + clip->width + 6; + + f32 tX = thumbX, tY = thumbY; + djui_gfx_position_translate(&tX, &tY); + create_dl_translation_matrix(DJUI_MTX_PUSH, tX, tY, 0); + + f32 tW = 4, tH = thumbH; + djui_gfx_scale_translate(&tW, &tH); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, tW, tH, 1.0f); + + gDPSetEnvColor(gDisplayListHead++, 200, 200, 200, 128); + gSPDisplayList(gDisplayListHead++, dl_djui_simple_rect); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + } + + return true; +} + +static void djui_flow_layout_on_scroll(struct DjuiBase* base, float x, float y) { + UNUSED f32 unused = x; + struct DjuiFlowLayout* layout = (struct DjuiFlowLayout*)base; + layout->scrollY -= y * 48.0f; +} + +static void flow_scroll_begin(struct DjuiBase* base, UNUSED bool inputCursor) { + struct DjuiFlowLayout* layout = (struct DjuiFlowLayout*)base; + layout->touchStartY = gCursorY; +} + +static void flow_scroll_update(struct DjuiBase* base) { + struct DjuiFlowLayout* layout = (struct DjuiFlowLayout*)base; + f32 delta = layout->touchStartY - gCursorY; + layout->scrollY += delta; + layout->touchStartY = gCursorY; +} + +static void flow_scroll_end(UNUSED struct DjuiBase* base) {} + +void djui_flow_layout_enable_scroll(struct DjuiFlowLayout* layout) { + layout->scrollEnabled = true; + layout->scrollY = 0; + layout->base.render = djui_flow_layout_scroll_render; + layout->base.abandonAfterChildRenderFail = false; + + if (!gDjuiFlowLayoutScrollRender) { + gDjuiFlowLayoutScrollRender = djui_flow_layout_scroll_render; + } + + djui_interactable_create(&layout->base, NULL); + djui_interactable_hook_scroll(&layout->base, djui_flow_layout_on_scroll); + djui_interactable_hook_cursor_down(&layout->base, + flow_scroll_begin, flow_scroll_update, flow_scroll_end); +} + static void djui_flow_layout_destroy(struct DjuiBase* base) { struct DjuiFlowLayout* layout = (struct DjuiFlowLayout*)base; free(layout); diff --git a/src/pc/djui/djui_flow_layout.h b/src/pc/djui/djui_flow_layout.h index 0c7dd75e2..dede55ab1 100644 --- a/src/pc/djui/djui_flow_layout.h +++ b/src/pc/djui/djui_flow_layout.h @@ -5,10 +5,18 @@ struct DjuiFlowLayout { struct DjuiBase base; enum DjuiFlowDirection flowDirection; struct DjuiScreenValue margin; + // Scroll + bool scrollEnabled; + f32 scrollY; + f32 contentHeight; + f32 touchStartY; }; +extern bool (*gDjuiFlowLayoutScrollRender)(struct DjuiBase*); + void djui_flow_layout_set_flow_direction(struct DjuiFlowLayout* layout, enum DjuiFlowDirection flowDirection); void djui_flow_layout_set_margin(struct DjuiFlowLayout* layout, f32 margin); void djui_flow_layout_set_margin_type(struct DjuiFlowLayout* layout, enum DjuiScreenValueType marginType); +void djui_flow_layout_enable_scroll(struct DjuiFlowLayout* layout); struct DjuiFlowLayout* djui_flow_layout_create(struct DjuiBase* parent); diff --git a/src/pc/djui/djui_interactable.c b/src/pc/djui/djui_interactable.c index 96f3acc06..be44f9e6b 100644 --- a/src/pc/djui/djui_interactable.c +++ b/src/pc/djui/djui_interactable.c @@ -5,6 +5,7 @@ #include "djui_panel_modlist.h" #include "djui_panel_playerlist.h" +#include "djui_flow_layout.h" #include "pc/controller/controller_sdl.h" #include "pc/controller/controller_mouse.h" #include "pc/controller/controller_keyboard.h" @@ -22,6 +23,8 @@ static enum PadHoldDirection sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; static u16 sKeyboardButtons = 0; static bool sIgnoreInteractableUntilCursorReleased = false; +static f32 sCursorDownStartY = 0; +static bool sDragScrollActive = false; struct DjuiBase* gDjuiHovered = NULL; struct DjuiBase* gDjuiCursorDownOn = NULL; @@ -336,10 +339,21 @@ void djui_interactable_on_text_editing(char* text, int cursorPos) { } void djui_interactable_on_scroll(float x, float y) { - if (gInteractableFocus == NULL) { return; } - if (gInteractableFocus->interactable == NULL) { return; } - if (gInteractableFocus->interactable->on_scroll == NULL) { return; } - gInteractableFocus->interactable->on_scroll(gInteractableFocus, x, y); + // Priority: focused element (e.g. chat input) + if (gInteractableFocus && gInteractableFocus->interactable + && gInteractableFocus->interactable->on_scroll) { + gInteractableFocus->interactable->on_scroll(gInteractableFocus, x, y); + return; + } + // Bubble from hovered element up to ancestors + struct DjuiBase* base = gDjuiHovered; + while (base) { + if (base->interactable && base->interactable->on_scroll) { + base->interactable->on_scroll(base, x, y); + return; + } + base = base->parent; + } } void djui_interactable_update_pad(void) { @@ -454,15 +468,44 @@ void djui_interactable_update(void) { if (gDjuiHovered != NULL) { gInteractableMouseDown = gDjuiHovered; gDjuiHovered = NULL; + sCursorDownStartY = gCursorY; + sDragScrollActive = false; djui_interactable_on_cursor_down_begin(gInteractableMouseDown, !mouseButtons); - } else { + } else if (sDragScrollActive) { djui_interactable_on_cursor_down(gInteractableMouseDown); + } else { + // check if vertical drag exceeds threshold to steal touch for scrolling + f32 dragDist = gCursorY - sCursorDownStartY; + if (dragDist < 0) { dragDist = -dragDist; } + if (gInteractableMouseDown != NULL && dragDist > 10.0f + && gDjuiFlowLayoutScrollRender + && (!gInteractableMouseDown->interactable + || !gInteractableMouseDown->interactable->on_cursor_down)) { + struct DjuiBase* parent = gInteractableMouseDown->parent; + while (parent) { + if (parent->render == gDjuiFlowLayoutScrollRender) { + gDjuiCursorDownOn = NULL; + djui_interactable_update_style(gInteractableMouseDown); + gInteractableMouseDown = parent; + sDragScrollActive = true; + djui_interactable_on_cursor_down_begin(parent, !mouseButtons); + break; + } + parent = parent->parent; + } + if (!sDragScrollActive) { + djui_interactable_on_cursor_down(gInteractableMouseDown); + } + } else { + djui_interactable_on_cursor_down(gInteractableMouseDown); + } } } else { // cursor up event if (gInteractableMouseDown != NULL) { djui_interactable_on_cursor_down_end(gInteractableMouseDown); gInteractableMouseDown = NULL; + sDragScrollActive = false; } struct DjuiBase* lastHovered = gDjuiHovered; gDjuiHovered = NULL; diff --git a/src/pc/djui/djui_panel_menu.c b/src/pc/djui/djui_panel_menu.c index 6ff2289a3..781cb8aef 100644 --- a/src/pc/djui/djui_panel_menu.c +++ b/src/pc/djui/djui_panel_menu.c @@ -101,6 +101,7 @@ struct DjuiThreePanel* djui_panel_menu_create(char* headerText, bool forcedLeftS djui_base_set_color(&body->base, 0, 0, 0, 0); djui_flow_layout_set_margin(body, 16); djui_flow_layout_set_flow_direction(body, DJUI_FLOW_DIR_DOWN); + djui_flow_layout_enable_scroll(body); } return panel; }