fix: fix scrolling cutoff for last entry in devexplorer
This commit is contained in:
@@ -118,7 +118,7 @@ struct ScrollMetrics {
|
|||||||
int list_h; // viewport height (view extent)
|
int list_h; // viewport height (view extent)
|
||||||
int total_h; // total content height (content extent)
|
int total_h; // total content height (content extent)
|
||||||
int max_scroll_px; // maximum pixel scroll offset
|
int max_scroll_px; // maximum pixel scroll offset
|
||||||
int scroll_px; // current pixel scroll offset (derived from scroll_y)
|
int scroll_px; // current pixel scroll offset (clamped scroll_y)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bounds of the toolbar "Refresh" button — shared by render and hit-testing.
|
// Bounds of the toolbar "Refresh" button — shared by render and hit-testing.
|
||||||
@@ -156,7 +156,7 @@ struct DevExplorerState {
|
|||||||
int dev_count;
|
int dev_count;
|
||||||
bool collapsed[NUM_CATEGORIES];
|
bool collapsed[NUM_CATEGORIES];
|
||||||
int selected_row;
|
int selected_row;
|
||||||
int scroll_y;
|
int scroll_y; // vertical scroll offset, in pixels (not rows)
|
||||||
uint64_t last_poll_ms;
|
uint64_t last_poll_ms;
|
||||||
|
|
||||||
int last_click_row;
|
int last_click_row;
|
||||||
|
|||||||
@@ -86,47 +86,23 @@ ScrollMetrics compute_scroll_metrics(DevExplorerState* de,
|
|||||||
if (max_scroll_px < 0) max_scroll_px = 0;
|
if (max_scroll_px < 0) max_scroll_px = 0;
|
||||||
m.max_scroll_px = max_scroll_px;
|
m.max_scroll_px = max_scroll_px;
|
||||||
|
|
||||||
|
// scroll_y is a pixel offset. Clamping to max_scroll_px (rather than a row
|
||||||
|
// boundary) lets the final scroll position sit the last row flush against
|
||||||
|
// the bottom edge, so a trailing category like Printers is never cut off.
|
||||||
if (de->scroll_y < 0) de->scroll_y = 0;
|
if (de->scroll_y < 0) de->scroll_y = 0;
|
||||||
if (de->scroll_y > row_count) de->scroll_y = row_count;
|
if (de->scroll_y > max_scroll_px) de->scroll_y = max_scroll_px;
|
||||||
|
|
||||||
int scroll_px = 0;
|
m.scroll_px = de->scroll_y;
|
||||||
for (int i = 0; i < de->scroll_y && i < row_count; i++)
|
|
||||||
scroll_px += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
|
||||||
|
|
||||||
// If the first visible row would push content past the end, snap scroll_y
|
|
||||||
// back to the furthest row that keeps the list fully populated.
|
|
||||||
if (scroll_px > max_scroll_px) {
|
|
||||||
de->scroll_y = 0;
|
|
||||||
int acc = 0;
|
|
||||||
for (int i = 0; i < row_count; i++) {
|
|
||||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
|
||||||
if (acc + rh > max_scroll_px) break;
|
|
||||||
acc += rh;
|
|
||||||
de->scroll_y = i + 1;
|
|
||||||
}
|
|
||||||
scroll_px = acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
m.scroll_px = scroll_px;
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maps a target pixel offset (from the dragged thumb) back to the nearest row
|
// Stores a target pixel offset (from the dragged thumb) directly. scroll_y is
|
||||||
// index, keeping scroll_y row-aligned like the keyboard and wheel navigation.
|
// pixel-space; compute_scroll_metrics clamps it to the content bounds.
|
||||||
void set_scroll_from_px(DevExplorerState* de, const DisplayRow* rows,
|
void set_scroll_from_px(DevExplorerState* de, const DisplayRow* rows,
|
||||||
int row_count, int target_px) {
|
int row_count, int target_px) {
|
||||||
if (target_px < 0) target_px = 0;
|
(void)rows;
|
||||||
|
(void)row_count;
|
||||||
int acc = 0;
|
de->scroll_y = target_px < 0 ? 0 : target_px;
|
||||||
int best_k = 0;
|
|
||||||
int best_diff = target_px; // distance for scroll_y == 0
|
|
||||||
for (int i = 0; i < row_count; i++) {
|
|
||||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
|
||||||
acc += rh;
|
|
||||||
int diff = gui_abs(acc - target_px);
|
|
||||||
if (diff < best_diff) { best_diff = diff; best_k = i + 1; }
|
|
||||||
}
|
|
||||||
de->scroll_y = best_k;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -155,9 +131,25 @@ static bool handle_list_click(int mx, int my) {
|
|||||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||||
int row_count = build_display_rows(&de, rows);
|
int row_count = build_display_rows(&de, rows);
|
||||||
|
|
||||||
|
// Locate the first visible row for the current pixel scroll offset, mirroring
|
||||||
|
// render_list() so click targets line up with what is drawn. The first row
|
||||||
|
// may be partially scrolled above the viewport (negative top offset).
|
||||||
|
int scroll_px = de.scroll_y;
|
||||||
|
int first_visible = 0, row_top = 0;
|
||||||
|
{
|
||||||
|
int acc = 0;
|
||||||
|
for (int i = 0; i < row_count; i++) {
|
||||||
|
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||||
|
if (acc + rh > scroll_px) { first_visible = i; row_top = acc; break; }
|
||||||
|
acc += rh;
|
||||||
|
first_visible = i + 1;
|
||||||
|
row_top = acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t now = montauk::get_milliseconds();
|
uint64_t now = montauk::get_milliseconds();
|
||||||
int cur_y = list_y;
|
int cur_y = list_y - (scroll_px - row_top);
|
||||||
for (int i = de.scroll_y; i < row_count; i++) {
|
for (int i = first_visible; i < row_count; i++) {
|
||||||
int row_h = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
int row_h = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||||
if (my >= cur_y && my < cur_y + row_h) {
|
if (my >= cur_y && my < cur_y + row_h) {
|
||||||
if (rows[i].type == ROW_CATEGORY) {
|
if (rows[i].type == ROW_CATEGORY) {
|
||||||
@@ -215,9 +207,9 @@ static bool handle_main_mouse(const Montauk::WinEvent& ev) {
|
|||||||
Rect track = mtk::scrollbar_track_rect(viewport);
|
Rect track = mtk::scrollbar_track_rect(viewport);
|
||||||
Rect thumb = mtk::scrollbar_thumb_rect(track, m.total_h, m.list_h, m.scroll_px);
|
Rect thumb = mtk::scrollbar_thumb_rect(track, m.total_h, m.list_h, m.scroll_px);
|
||||||
|
|
||||||
// Mouse wheel scrolls by rows.
|
// Mouse wheel scrolls by roughly one row per notch (scroll_y is pixels).
|
||||||
if (ev.mouse.scroll != 0) {
|
if (ev.mouse.scroll != 0) {
|
||||||
de.scroll_y += ev.mouse.scroll;
|
de.scroll_y += ev.mouse.scroll * ITEM_H;
|
||||||
if (de.scroll_y < 0) de.scroll_y = 0;
|
if (de.scroll_y < 0) de.scroll_y = 0;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -291,21 +283,23 @@ static bool handle_key(const Montauk::KeyEvent& key) {
|
|||||||
if (key.scancode == 0x48) { // Up
|
if (key.scancode == 0x48) { // Up
|
||||||
if (de.selected_row <= 0) de.selected_row = 0;
|
if (de.selected_row <= 0) de.selected_row = 0;
|
||||||
else de.selected_row--;
|
else de.selected_row--;
|
||||||
if (de.selected_row < de.scroll_y)
|
// Scroll up just enough to bring the selected row's top into view.
|
||||||
de.scroll_y = de.selected_row;
|
int sel_top = 0;
|
||||||
|
for (int i = 0; i < de.selected_row && i < row_count; i++)
|
||||||
|
sel_top += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||||
|
if (sel_top < de.scroll_y)
|
||||||
|
de.scroll_y = sel_top;
|
||||||
} else if (key.scancode == 0x50) { // Down
|
} else if (key.scancode == 0x50) { // Down
|
||||||
if (de.selected_row < row_count - 1)
|
if (de.selected_row < row_count - 1)
|
||||||
de.selected_row++;
|
de.selected_row++;
|
||||||
|
// Scroll down just enough to bring the selected row's bottom into view.
|
||||||
|
int sel_top = 0;
|
||||||
|
for (int i = 0; i < de.selected_row && i < row_count; i++)
|
||||||
|
sel_top += (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||||
|
int sel_h = (rows[de.selected_row].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||||
int list_h = g_win_h - TOOLBAR_H;
|
int list_h = g_win_h - TOOLBAR_H;
|
||||||
int cur_h = 0, last_visible = de.scroll_y;
|
if (sel_top + sel_h > de.scroll_y + list_h)
|
||||||
for (int i = de.scroll_y; i < row_count; i++) {
|
de.scroll_y = sel_top + sel_h - list_h;
|
||||||
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
|
||||||
if (cur_h + rh > list_h) break;
|
|
||||||
cur_h += rh;
|
|
||||||
last_visible = i;
|
|
||||||
}
|
|
||||||
if (de.selected_row > last_visible)
|
|
||||||
de.scroll_y += (de.selected_row - last_visible);
|
|
||||||
} else if (key.scancode == 0x4B) { // Left — collapse
|
} else if (key.scancode == 0x4B) { // Left — collapse
|
||||||
if (de.selected_row >= 0 && de.selected_row < row_count) {
|
if (de.selected_row >= 0 && de.selected_row < row_count) {
|
||||||
int cat = rows[de.selected_row].category;
|
int cat = rows[de.selected_row].category;
|
||||||
|
|||||||
@@ -77,9 +77,27 @@ static void render_list(uint32_t* px) {
|
|||||||
|
|
||||||
if (de.selected_row >= row_count) de.selected_row = row_count - 1;
|
if (de.selected_row >= row_count) de.selected_row = row_count - 1;
|
||||||
|
|
||||||
|
// Locate the first visible row for the current pixel scroll offset. The top
|
||||||
|
// row may be partially scrolled above the viewport (its top is then above
|
||||||
|
// list_y); render() draws the toolbar after the list so that overhang is
|
||||||
|
// masked. Clamping the scroll offset in pixels (not whole rows) lets the
|
||||||
|
// final row sit flush with the bottom edge, so the last category is never
|
||||||
|
// cut off.
|
||||||
|
int scroll_px = m.scroll_px;
|
||||||
|
int first_visible = 0, row_top = 0;
|
||||||
|
{
|
||||||
|
int acc = 0;
|
||||||
|
for (int i = 0; i < row_count; i++) {
|
||||||
|
int rh = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||||
|
if (acc + rh > scroll_px) { first_visible = i; row_top = acc; break; }
|
||||||
|
acc += rh;
|
||||||
|
first_visible = i + 1;
|
||||||
|
row_top = acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw rows
|
// Draw rows
|
||||||
int first_visible = de.scroll_y;
|
int cur_y = list_y - (scroll_px - row_top);
|
||||||
int cur_y = list_y;
|
|
||||||
Color menu_hover = Color::from_rgb(0xD0, 0xE0, 0xF8);
|
Color menu_hover = Color::from_rgb(0xD0, 0xE0, 0xF8);
|
||||||
|
|
||||||
for (int i = first_visible; i < row_count; i++) {
|
for (int i = first_visible; i < row_count; i++) {
|
||||||
@@ -168,7 +186,9 @@ static void render_list(uint32_t* px) {
|
|||||||
|
|
||||||
void render(uint32_t* pixels) {
|
void render(uint32_t* pixels) {
|
||||||
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
|
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
|
||||||
render_toolbar(pixels);
|
// List first, toolbar last: a partially-scrolled top row can extend above
|
||||||
|
// the list area, and drawing the toolbar afterward masks that overhang.
|
||||||
render_list(pixels);
|
render_list(pixels);
|
||||||
|
render_toolbar(pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user