Skip to content

Commit 99439ea

Browse files
committed
✨ VFXScrollPane: allow padding both standard and virtualized contents
Signed-off-by: palexdev <alessandro.parisi406@gmail.com>
1 parent 2393f65 commit 99439ea

3 files changed

Lines changed: 92 additions & 38 deletions

File tree

‎src/main/java/io/github/palexdev/virtualizedfx/controls/VFXScrollPane.java‎

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import javafx.css.Styleable;
5050
import javafx.css.StyleablePropertyFactory;
5151
import javafx.geometry.Bounds;
52+
import javafx.geometry.Insets;
5253
import javafx.geometry.Orientation;
5354
import javafx.geometry.Pos;
5455
import javafx.scene.Node;
@@ -159,6 +160,8 @@ private void init() {
159160
/**
160161
* @return the appropriate sizes for the scroll pane's content. For virtualized containers ({@link VFXContainer})
161162
* the values are given by the {@link VFXContainer#virtualMaxXProperty()} and the {@link VFXContainer#virtualMaxYProperty()}
163+
* <p></p>
164+
* For standard nodes returns their bounds including the padding specified by the {@link #contentPaddingProperty()}.
162165
*/
163166
public Size getContentBounds() {
164167
Node content = getContent();
@@ -171,7 +174,11 @@ public Size getContentBounds() {
171174
);
172175
default -> {
173176
Bounds b = content.getLayoutBounds();
174-
yield Size.of(b.getWidth(), b.getHeight());
177+
Insets padding = getContentPadding();
178+
yield Size.of(
179+
b.getWidth() + padding.getLeft() + padding.getRight(),
180+
b.getHeight() + padding.getTop() + padding.getBottom()
181+
);
175182
}
176183
};
177184
}
@@ -237,6 +244,13 @@ protected void invalidated() {
237244
false
238245
);
239246

247+
private final StyleableObjectProperty<Insets> contentPadding = new StyleableObjectProperty<>(
248+
StyleableProperties.CONTENT_PADDING,
249+
this,
250+
"contentPadding",
251+
Insets.EMPTY
252+
);
253+
240254
private final StyleableObjectProperty<VBarPos> vBarPos = new StyleableObjectProperty<>(
241255
StyleableProperties.VBAR_POS,
242256
this,
@@ -462,6 +476,32 @@ public void setFitToHeight(boolean fitToHeight) {
462476
this.fitToHeight.set(fitToHeight);
463477
}
464478

479+
public Insets getContentPadding() {
480+
return contentPadding.get();
481+
}
482+
483+
/**
484+
* Specifies the padding around the content.
485+
* <p>
486+
* This is also settable via CSS with the "-vfx-content-padding" property.
487+
* <p></p>
488+
* This is a very delicate feature because in a scroll pane layout computations (sizes, positions and whatnot) must
489+
* be precise/exact, otherwise the content may not be fully scrollable or properly aligned.
490+
* <p>
491+
* This is implemented in the simplest way possible. The padding is included in the content bounds returned by
492+
* {@link #getContentBounds()} for standard nodes. For virtualized containers the padding is taken into account
493+
* only during layout.
494+
* <p>
495+
* That said, I consider this an <b>experimental</b> feature, expect bugs, use with cautions.
496+
*/
497+
public StyleableObjectProperty<Insets> contentPaddingProperty() {
498+
return contentPadding;
499+
}
500+
501+
public void setContentPadding(Insets contentPadding) {
502+
this.contentPadding.set(contentPadding);
503+
}
504+
465505
public VBarPos getVBarPos() {
466506
return vBarPos.get();
467507
}
@@ -876,6 +916,13 @@ private static class StyleableProperties {
876916
false
877917
);
878918

919+
private static final CssMetaData<VFXScrollPane, Insets> CONTENT_PADDING =
920+
FACTORY.createInsetsCssMetaData(
921+
"-vfx-content-padding",
922+
VFXScrollPane::contentPaddingProperty,
923+
Insets.EMPTY
924+
);
925+
879926
private static final CssMetaData<VFXScrollPane, VBarPos> VBAR_POS =
880927
FACTORY.createEnumCssMetaData(
881928
ScrollPaneEnums.VBarPos.class,
@@ -1018,7 +1065,7 @@ private static class StyleableProperties {
10181065
cssMetaDataList = StyleUtils.cssMetaDataList(
10191066
MFXControl.getClassCssMetaData(),
10201067
LAYOUT_MODE, ALIGNMENT, MAIN_AXIS,
1021-
FIT_TO_WIDTH, FIT_TO_HEIGHT,
1068+
FIT_TO_WIDTH, FIT_TO_HEIGHT, CONTENT_PADDING,
10221069
VBAR_POS, HBAR_POS, SCROLL_BARS_GAP,
10231070
AUTO_HIDE_BARS, MIN_BARS_OPACITY, MAX_BARS_OPACITY,
10241071
VBAR_POLICY, HBAR_POLICY,

‎src/main/java/io/github/palexdev/virtualizedfx/controls/skins/VFXScrollPaneSkin.java‎

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import javafx.animation.Animation;
2929
import javafx.animation.Interpolator;
3030
import javafx.application.Platform;
31-
import javafx.beans.InvalidationListener;
3231
import javafx.beans.Observable;
3332
import javafx.beans.property.SimpleObjectProperty;
3433
import javafx.beans.value.ObservableValue;
@@ -43,7 +42,6 @@
4342
import javafx.util.Duration;
4443

4544
import static io.github.palexdev.mfxcore.input.WhenEvent.intercept;
46-
import static io.github.palexdev.mfxcore.observables.OnInvalidated.withListener;
4745
import static io.github.palexdev.mfxcore.observables.When.*;
4846

4947
/**
@@ -138,7 +136,6 @@ protected void layoutChildren() {
138136
*/
139137
private void addListeners() {
140138
VFXScrollPane pane = getSkinnable();
141-
InvalidationListener ll = l -> pane.requestLayout();
142139

143140
// Bindings
144141
bvp = new BarsVisibilityProperty();
@@ -184,11 +181,12 @@ private void addListeners() {
184181
.executeNow(() -> pane.getContent() != null),
185182

186183
// Layout
187-
withListener(pane.fitToWidthProperty(), ll),
188-
withListener(pane.fitToHeightProperty(), ll),
189-
withListener(pane.vBarPosProperty(), ll),
190-
withListener(pane.hBarPosProperty(), ll),
191-
withListener(pane.scrollBarsGapProperty(), ll),
184+
observe(
185+
pane::requestLayout,
186+
pane.fitToWidthProperty(), pane.fitToHeightProperty(), pane.contentPaddingProperty(),
187+
pane.vBarPosProperty(), pane.hBarPosProperty(),
188+
pane.scrollBarsGapProperty()
189+
),
192190
// Animations
193191
onInvalidated(pane.minBarsOpacityProperty())
194192
.then(v -> buildBarsAnimations())
@@ -228,13 +226,16 @@ protected void layoutContent() {
228226

229227
double w = viewport.getWidth();
230228
double h = viewport.getHeight();
229+
Insets padding = pane.getContentPadding();
231230
if (content instanceof VFXContainer<?>) {
232231
// Virtualized containers always take up all the space and thus ignore the alignment too
233-
content.resizeRelocate(0, 0, w, h);
232+
layoutInArea(content, 0, 0, w, h, 0, padding, HPos.LEFT, VPos.TOP);
234233
} else {
235234
Pos alignment = pane.getAlignment();
236235
VPos vAlign = alignment.getVpos();
237236
HPos hAlign = alignment.getHpos();
237+
238+
// Find content bounds and set it to be at least the same as the viewport size if fitTo configs are enabled
238239
double cw = LayoutUtils.snappedBoundWidth(content);
239240
double ch = LayoutUtils.snappedBoundHeight(content);
240241
cw = pane.isFitToWidth()
@@ -244,12 +245,12 @@ protected void layoutContent() {
244245
? Math.max(h, ch)
245246
: ch;
246247

247-
// If the content is larger than the viewport then the alignment is ignored
248-
if (ch > h) vAlign = VPos.TOP;
248+
// If the content is larger than the viewport, then the alignment is ignored
249249
if (cw > w) hAlign = HPos.LEFT;
250+
if (ch > h) vAlign = VPos.TOP;
250251

251252
content.resize(cw, ch);
252-
positionInArea(content, 0, 0, w, h, 0, hAlign, vAlign);
253+
positionInArea(content, 0, 0, w, h, 0, padding, hAlign, vAlign);
253254
}
254255

255256
updateVisualAmount(content);
@@ -276,24 +277,27 @@ protected void updateScrollBindings(Node oldContent, Node newContent) {
276277
if (newContent == null) return;
277278

278279
if (!(newContent instanceof VFXContainer<?> c)) {
280+
VFXScrollPane pane = getSkinnable();
279281
newContent.translateXProperty().bind(DoubleBindingBuilder.build()
280282
.setMapper(() -> {
281-
double cw = newContent.getLayoutBounds().getWidth();
283+
double cw = pane.getContentBounds().width();
282284
double vw = viewport.getWidth();
283285
double maxScroll = Math.max(0, cw - vw);
284286
return -maxScroll * hBar.getValue();
285287
})
288+
.addSources(pane.contentPaddingProperty())
286289
.addSources(newContent.layoutBoundsProperty(), viewport.widthProperty())
287290
.addSources(hBar.valueProperty())
288291
.get()
289292
);
290293
newContent.translateYProperty().bind(DoubleBindingBuilder.build()
291294
.setMapper(() -> {
292-
double ch = newContent.getLayoutBounds().getHeight();
295+
double ch = pane.getContentBounds().height();
293296
double vh = viewport.getHeight();
294297
double maxScroll = Math.max(0, ch - vh);
295298
return -maxScroll * vBar.getValue();
296299
})
300+
.addSources(pane.contentPaddingProperty())
297301
.addSources(newContent.layoutBoundsProperty(), viewport.heightProperty())
298302
.addSources(vBar.valueProperty())
299303
.get()

‎src/test/java/app/Playground.java‎

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,22 @@
2323
import java.nio.charset.StandardCharsets;
2424

2525
import fr.brouillard.oss.cssfx.CSSFX;
26-
import interactive.grid.GridTestUtils;
2726
import io.github.palexdev.mfxcore.builders.InsetsBuilder;
27+
import io.github.palexdev.mfxcore.utils.fx.ColorUtils;
2828
import io.github.palexdev.virtualizedfx.VFXResources;
2929
import io.github.palexdev.virtualizedfx.base.VFXScrollable;
3030
import io.github.palexdev.virtualizedfx.controls.VFXScrollPane;
3131
import io.github.palexdev.virtualizedfx.utils.ScrollParams;
3232
import javafx.application.Application;
3333
import javafx.geometry.Pos;
3434
import javafx.scene.Scene;
35+
import javafx.scene.control.Label;
36+
import javafx.scene.layout.Border;
3537
import javafx.scene.layout.StackPane;
38+
import javafx.scene.shape.Rectangle;
3639
import javafx.stage.Stage;
3740

3841
import static src.utils.Utils.debugView;
39-
import static src.utils.Utils.items;
4042

4143
public class Playground extends Application {
4244
private static final String LOREM;
@@ -58,22 +60,29 @@ public void start(Stage primaryStage) {
5860
pane.setAlignment(Pos.CENTER);
5961
pane.setPadding(InsetsBuilder.uniform(4.0).get());
6062

61-
/* TableTestUtils.Table table = new TableTestUtils.Table(users(100));
62-
table.setColumnsWidth(50.0);
63-
table.setColumnsLayoutMode(ColumnsLayoutMode.VARIABLE);
64-
table.autosizeColumns();*/
63+
//TableTestUtils.Table table = new TableTestUtils.Table(users(10000));
64+
//table.setColumnsWidth(50.0);
65+
//table.setColumnsLayoutMode(ColumnsLayoutMode.VARIABLE);
66+
//table.autosizeColumns();
6567

66-
/* ListTestUtils.List container = new ListTestUtils.List(items(100));
67-
container.setFitToViewport(false);*/
68+
/* ListTestUtils.List list = new ListTestUtils.List(items(100));
69+
list.setFitToViewport(false);*/
6870

69-
GridTestUtils.Grid container = new GridTestUtils.Grid(items(500));
70-
container.setColumnsNum(20);
71+
//GridTestUtils.Grid grid = new GridTestUtils.Grid(items(500));
72+
//grid.setColumnsNum(5);
7173

72-
VFXScrollPane sp = new VFXScrollPane(container);
73-
//sp.setSmoothScroll(true);
74-
//sp.setDragToScroll(true);
74+
Label label = new Label(LOREM);
75+
label.setBorder(Border.stroke(ColorUtils.getRandomColor()));
76+
77+
Rectangle rt = new Rectangle(2000, 2000, ColorUtils.getRandomColor());
78+
79+
VFXScrollPane sp = new VFXScrollPane(label);
80+
sp.setSmoothScroll(true);
81+
sp.setFitToWidth(true);
82+
sp.setFitToHeight(true);
83+
sp.setDragToScroll(true);
7584
//sp.setDragSmoothScroll(true);
76-
sp.setShowButtons(true);
85+
//sp.setShowButtons(true);
7786
//sp.setVBarPolicy(ScrollPaneEnums.ScrollBarPolicy.NEVER);
7887
//sp.setLayoutMode(ScrollPaneEnums.LayoutMode.COMPACT);
7988
//Platform.runLater(() -> sp.setPadding(InsetsBuilder.uniform(4.0).withTop(40.0).get()));
@@ -82,13 +91,7 @@ public void start(Stage primaryStage) {
8291
//sp.setScrollBarsPos(Pos.TOP_RIGHT);
8392
//sp.setVUnitIncrement(0.025);
8493
//sp.setHUnitIncrement(0.05);
85-
86-
/*
87-
Label label = new Label(LOREM);
88-
sp = new VFXScrollPane(label);
89-
*/
90-
91-
VFXScrollable.bindSpeed(sp, ScrollParams.cells(1.25), ScrollParams.percentage(0.05));
94+
VFXScrollable.bindSpeed(sp, ScrollParams.percentage(0.2), ScrollParams.percentage(0.2));
9295

9396
pane.getChildren().addAll(sp);
9497
Scene scene = new Scene(pane, 600, 400);
@@ -98,7 +101,7 @@ public void start(Stage primaryStage) {
98101

99102
debugView(null, pane);
100103

101-
sp.getStylesheets().add(VFXResources.loadResource("VFXScrollBar.css"));
104+
sp.getStylesheets().add(VFXResources.loadResource("VFXScrollPane.css"));
102105
CSSFX.start(sp);
103106
}
104107
}

0 commit comments

Comments
 (0)