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 total_h; // total content height (content extent)
|
||||
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.
|
||||
@@ -156,7 +156,7 @@ struct DevExplorerState {
|
||||
int dev_count;
|
||||
bool collapsed[NUM_CATEGORIES];
|
||||
int selected_row;
|
||||
int scroll_y;
|
||||
int scroll_y; // vertical scroll offset, in pixels (not rows)
|
||||
uint64_t last_poll_ms;
|
||||
|
||||
int last_click_row;
|
||||
|
||||
@@ -86,47 +86,23 @@ ScrollMetrics compute_scroll_metrics(DevExplorerState* de,
|
||||
if (max_scroll_px < 0) max_scroll_px = 0;
|
||||
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 > row_count) de->scroll_y = row_count;
|
||||
if (de->scroll_y > max_scroll_px) de->scroll_y = max_scroll_px;
|
||||
|
||||
int scroll_px = 0;
|
||||
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;
|
||||
m.scroll_px = de->scroll_y;
|
||||
return m;
|
||||
}
|
||||
|
||||
// Maps a target pixel offset (from the dragged thumb) back to the nearest row
|
||||
// index, keeping scroll_y row-aligned like the keyboard and wheel navigation.
|
||||
// Stores a target pixel offset (from the dragged thumb) directly. scroll_y is
|
||||
// pixel-space; compute_scroll_metrics clamps it to the content bounds.
|
||||
void set_scroll_from_px(DevExplorerState* de, const DisplayRow* rows,
|
||||
int row_count, int target_px) {
|
||||
if (target_px < 0) target_px = 0;
|
||||
|
||||
int acc = 0;
|
||||
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;
|
||||
(void)rows;
|
||||
(void)row_count;
|
||||
de->scroll_y = target_px < 0 ? 0 : target_px;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -155,9 +131,25 @@ static bool handle_list_click(int mx, int my) {
|
||||
DisplayRow rows[MAX_DISPLAY_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();
|
||||
int cur_y = list_y;
|
||||
for (int i = de.scroll_y; i < row_count; i++) {
|
||||
int cur_y = list_y - (scroll_px - row_top);
|
||||
for (int i = first_visible; i < row_count; i++) {
|
||||
int row_h = (rows[i].type == ROW_CATEGORY) ? CAT_H : ITEM_H;
|
||||
if (my >= cur_y && my < cur_y + row_h) {
|
||||
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 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) {
|
||||
de.scroll_y += ev.mouse.scroll;
|
||||
de.scroll_y += ev.mouse.scroll * ITEM_H;
|
||||
if (de.scroll_y < 0) de.scroll_y = 0;
|
||||
changed = true;
|
||||
}
|
||||
@@ -291,21 +283,23 @@ static bool handle_key(const Montauk::KeyEvent& key) {
|
||||
if (key.scancode == 0x48) { // Up
|
||||
if (de.selected_row <= 0) de.selected_row = 0;
|
||||
else de.selected_row--;
|
||||
if (de.selected_row < de.scroll_y)
|
||||
de.scroll_y = de.selected_row;
|
||||
// Scroll up just enough to bring the selected row's top 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;
|
||||
if (sel_top < de.scroll_y)
|
||||
de.scroll_y = sel_top;
|
||||
} else if (key.scancode == 0x50) { // Down
|
||||
if (de.selected_row < row_count - 1)
|
||||
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 cur_h = 0, last_visible = de.scroll_y;
|
||||
for (int i = de.scroll_y; i < row_count; i++) {
|
||||
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);
|
||||
if (sel_top + sel_h > de.scroll_y + list_h)
|
||||
de.scroll_y = sel_top + sel_h - list_h;
|
||||
} else if (key.scancode == 0x4B) { // Left — collapse
|
||||
if (de.selected_row >= 0 && de.selected_row < row_count) {
|
||||
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;
|
||||
|
||||
// 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
|
||||
int first_visible = de.scroll_y;
|
||||
int cur_y = list_y;
|
||||
int cur_y = list_y - (scroll_px - row_top);
|
||||
Color menu_hover = Color::from_rgb(0xD0, 0xE0, 0xF8);
|
||||
|
||||
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) {
|
||||
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_toolbar(pixels);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user