Skip to content

Commit 776bd07

Browse files
committed
Add scrollToIfNeeded method
1 parent f0e0592 commit 776bd07

4 files changed

Lines changed: 145 additions & 10 deletions

File tree

toolkit-fx/src/main/java/com/techsenger/toolkit/fx/utils/ListViewUtils.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
public final class ListViewUtils {
2727

2828
/**
29-
* Scrolls the list view only when the given index is outside the visible range, mimicking natural keyboard
29+
* Scrolls the list view only when the given index is outside the fully visible range, mimicking natural keyboard
3030
* navigation behavior.
3131
*
3232
* @param listView the list view to scroll
@@ -37,15 +37,7 @@ public static void scrollToIfNeeded(ListView<?> listView, int index) {
3737
if (flow == null) {
3838
return;
3939
}
40-
41-
int first = flow.getFirstVisibleCell().getIndex();
42-
int last = flow.getLastVisibleCell().getIndex();
43-
44-
if (index < first) {
45-
listView.scrollTo(index);
46-
} else if (index > last) {
47-
listView.scrollTo(index - (last - first));
48-
}
40+
NodeUtils.scrollToIfNeeded(flow, listView::scrollTo, index);
4941
}
5042

5143
private ListViewUtils() {

toolkit-fx/src/main/java/com/techsenger/toolkit/fx/utils/NodeUtils.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Objects;
21+
import java.util.function.IntConsumer;
2122
import javafx.animation.KeyFrame;
2223
import javafx.animation.Timeline;
2324
import javafx.scene.Node;
2425
import javafx.scene.Parent;
26+
import javafx.scene.control.skin.VirtualFlow;
2527
import javafx.util.Duration;
2628
import org.slf4j.Logger;
2729
import org.slf4j.LoggerFactory;
@@ -96,6 +98,39 @@ public static ArrayList<Node> getAllNodes(final Parent parent) {
9698
return nodes;
9799
}
98100

101+
/**
102+
* Scrolls the control only when the given index is outside the fully visible range,
103+
* mimicking natural keyboard navigation behavior. A partially visible cell at the
104+
* bottom is not considered visible.
105+
*
106+
* @param flow the virtual flow of the control
107+
* @param scroll the scroll action to perform
108+
* @param index the index that should be visible
109+
*/
110+
static void scrollToIfNeeded(VirtualFlow<?> flow, IntConsumer scroll, int index) {
111+
var firstCell = flow.getFirstVisibleCell();
112+
var lastCell = flow.getLastVisibleCell();
113+
if (firstCell == null || lastCell == null) {
114+
return;
115+
}
116+
int first = firstCell.getIndex();
117+
int last = lastCell.getBoundsInParent().getMaxY() > flow.getHeight()
118+
? lastCell.getIndex() - 1
119+
: lastCell.getIndex();
120+
int visibleCount = last - first;
121+
if (index <= first) {
122+
scroll.accept(index);
123+
} else if (index > last) {
124+
if (index - last > 1) {
125+
// Jump - scroll so that index is the last visible item
126+
scroll.accept(index - visibleCount);
127+
} else {
128+
// Single step down - shift by one
129+
scroll.accept(first + 1);
130+
}
131+
}
132+
}
133+
99134
private static void doRequestFocus(Node node, Runnable onSuccess, int maxAttempts) {
100135
Timeline[] holder = new Timeline[1];
101136
holder[0] = new Timeline(
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2016-2025 Pavel Castornii.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.techsenger.toolkit.fx.utils;
18+
19+
import javafx.scene.control.TableView;
20+
import javafx.scene.control.TreeTableView;
21+
import javafx.scene.control.skin.VirtualFlow;
22+
23+
/**
24+
*
25+
* @author Pavel Castornii
26+
*/
27+
public final class TableUtils {
28+
29+
/**
30+
* Scrolls the table view only when the given index is outside the visible range, mimicking natural keyboard
31+
* navigation behavior.
32+
*
33+
* @param tableView the table view to scroll
34+
* @param index the index that should be visible
35+
*/
36+
public static void scrollToIfNeeded(TableView<?> tableView, int index) {
37+
VirtualFlow<?> flow = (VirtualFlow<?>) tableView.lookup(".virtual-flow");
38+
if (flow == null) {
39+
return;
40+
}
41+
NodeUtils.scrollToIfNeeded(flow, tableView::scrollTo, index);
42+
}
43+
44+
/**
45+
* Scrolls the tree table view only when the given index is outside the visible range, mimicking natural keyboard
46+
* navigation behavior.
47+
*
48+
* @param treeTableView the tree table view to scroll
49+
* @param index the index that should be visible
50+
*/
51+
public static void scrollToIfNeeded(TreeTableView<?> treeTableView, int index) {
52+
VirtualFlow<?> flow = (VirtualFlow<?>) treeTableView.lookup(".virtual-flow");
53+
if (flow == null) {
54+
return;
55+
}
56+
NodeUtils.scrollToIfNeeded(flow, treeTableView::scrollTo, index);
57+
}
58+
59+
private TableUtils() {
60+
// empty
61+
}
62+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2016-2025 Pavel Castornii.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.techsenger.toolkit.fx.utils;
18+
19+
import javafx.scene.control.TreeView;
20+
import javafx.scene.control.skin.VirtualFlow;
21+
22+
/**
23+
*
24+
* @author Pavel Castornii
25+
*/
26+
public final class TreeViewUtils {
27+
28+
/**
29+
* Scrolls the tree view only when the given index is outside the fully visible range, mimicking natural keyboard
30+
* navigation behavior. Partially visible cells are not considered visible.
31+
*
32+
* @param treeView the tree view to scroll
33+
* @param index the index that should be visible
34+
*/
35+
public static void scrollToIfNeeded(TreeView<?> treeView, int index) {
36+
VirtualFlow<?> flow = (VirtualFlow<?>) treeView.lookup(".virtual-flow");
37+
if (flow == null) {
38+
return;
39+
}
40+
NodeUtils.scrollToIfNeeded(flow, treeView::scrollTo, index);
41+
}
42+
43+
private TreeViewUtils() {
44+
// empty
45+
}
46+
}

0 commit comments

Comments
 (0)