This commit is contained in:
Leonardo Manrique 2026-04-04 22:43:54 -04:00 committed by GitHub
commit 83ddeffd76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 169 additions and 5 deletions

View file

@ -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;
}
}
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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;
}