Class ScrollView
- All Implemented Interfaces:
Scrollable
ScrollBar). The
child is physically positioned at (-offsetX, -offsetY), so hit-testing and
event coordinates work unchanged.
It clips exactly on an axis it scrolls while the content overflows it there, and
nowhere else. Where the content scrolls, what lies past the viewport's edge is out of sight
and is cut there. Across an axis that does not scroll, or one the content fits, nothing can
move out of sight, so the content may paint past the viewport by a band, the reach of the
theme's focus ring unless paintOverflow says otherwise: a control flush with the side
of a view that scrolls only vertically keeps its whole focus ring, and so does one flush with
its bottom until the content grows past the viewport.
Scroll indicators are the shared ScrollBar (draggable, themed,
policy-driven), the same component every scroller uses.
The bars either float over the content or take a gutter of their own
(setBarLayout(limn.components.ScrollGutters.Layout)). Which one is right is a property of what is being
scrolled, not a taste: an image, a video or a page of prose wants every point of
width and loses nothing to a bar that fades over it, while a table, a form or a
tree wants its last column readable at all times, and a bar sitting on it is a
defect however briefly it shows.
-
Constructor Summary
ConstructorsConstructorDescriptionScrollView(Widget<?> child) Wrapschild, scrolling vertically as its content overflows: clipped at the top and bottom while it does, and let past the sides, and past the top and bottom while it fits, bypaintOverflow's band.ScrollView(Widget<?> child, boolean horizontal, boolean vertical) Wrapschild, scrolling only on the axes enabled here. -
Method Summary
Modifier and TypeMethodDescriptionWhether the scrollbars overlay the content or reserve a gutter.protected floatclipHeight(Widget<?> forChild) The height of the rectangle this widget clipschildto; seeWidget.clipX(Widget).protected booleanWhetherWidget.paintChildren(limn.graphics.Canvas)clips its children to this widget's bounds (scroll views, list viewports, tab strips).protected floatThe width of the rectangle this widget clipschildto; seeWidget.clipX(Widget).protected floatThe left edge, in this widget's own coordinates, of the rectangleWidget.clipsChildren()clipschildto.protected floatThe top edge of the rectangle this widget clipschildto; seeWidget.clipX(Widget).floatLargest validoffsetX();0when the content fits.floatLargest validoffsetY();0when the content fits.floatoffsetX()Horizontal scroll position in logical points,0at the leading edge: the left edge in a left-to-right subtree and the right edge in a right-to-left one.floatoffsetY()Vertical scroll position in logical points,0at the top.protected voidOne scroll pane, whether or not anything overflows today, carrying where the viewport sits in the content and how much of it shows on each axis.protected voidonKeyEvent(KeyEvent event) Key events (focused widget first, then ancestors).protected voidonLayout()Containers position children here (measure +Widget.layoutBox(float, float, float, float)per child).protected SizeonMeasure(Constraints constraints) Reports the size this widget wants withinconstraints.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.protected voidpaintChildren(Canvas canvas) Children pass; override to clip (e.g.protected floatThe band across an axis this view does not scroll or its content fits, which its content may paint into past the box; nothing while the content overflows it both ways, when the viewport bounds everything.paintOverflow(float points) Declares how far the content may paint past the viewport across an axis this view does not scroll, or one its content fits, in logical points, in place of the reach the theme gives a focus ring.Goes back to letting the content paint past the viewport by the reach of the theme's focus ring across an axis this view does not scroll or its content fits, which is the default; seepaintOverflow(float).voidrevealRect(float x, float y, float rectWidth, float rectHeight) Scrolls the minimum so the rect (in viewport coordinates) becomes visible.voidscrollBy(float dx, float dy) Scrolls by a delta in logical points (clamped).voidscrollTo(float newOffsetX, float newOffsetY) Scrolls to an absolute offset (clamped to the content).setBarInsets(Insets insets) Sets the bars' tracks in from the ends of their sides, for content that scrolls under something laid over it: a bar of glass across the top that the content passes beneath keeps its own thumb out from under the bar with the bar's height as the top inset.setBarLayout(ScrollGutters.Layout layout) Sets whether the bars float over the content or reserve a strip of their own (defaultScrollGutters.Layout.OVERLAY).setScrollbarPolicy(ScrollBar.Policy policy) Sets the scrollbar visibility policy (defaultScrollBar.Policy.AUTO).floatVisible height in logical points, excluding anything the content overflows by.floatThe width content is actually shown in: the box, less any reserved gutter.Methods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffset, baselineOffsetOf, caretRect, children, clipboard, controlSize, cursor, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedBackward, focusArrivedByTraversal, handleUserChange, height, hitTest, hitTestChild, id, imageCursor, inheritanceHost, invalidate, invalidate, invalidateAccessible, isEnabled, isFocusable, isFocused, isRightToLeft, isSelfEnabled, isShowing, isVisible, isVisibleByItsOwnFlag, layoutBox, layoutDirection, locale, localToScene, localToSceneBounds, markNeedsContainedLayout, markNeedsLayout, markNeedsLayoutInPlace, maxHeight, maxWidth, measure, minHeight, minWidth, moveChild, moveChild, moveChild, neutralBase, notifyChange, notifyTextEdit, observeChanges, onAccessibilityAction, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onCanEdit, onCharTyped, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onEdit, onFocusGained, onFocusLost, onHitTest, onPaint, onPaintOverlay, onPreedit, onSyntheticAction, overlayPassesPointer, paintsDecoration, paintsFromBackdrop, paintWidget, parent, remove, remove, requestFocus, requestFocus, revealInView, revealInView, runHandler, scene, sceneNanos, sceneToLocal, self, setAccessibleDescribedBy, setAccessibleDescription, setAccessibleDescription, setAccessibleIgnored, setAccessibleLabelledBy, setAccessibleName, setAccessibleName, setAccessibleRole, setControlSize, setCursor, setEnabled, setFocusable, setId, setImageCursor, setInheritanceHost, setLayoutDirection, setLocale, setMaxHeight, setMaxWidth, setMinHeight, setMinWidth, setPresentedElsewhere, setTooltip, setTooltip, setVisible, shapeText, textRuler, tooltip, tooltipSource, width, x, y
-
Constructor Details
-
ScrollView
Wrapschild, scrolling vertically as its content overflows: clipped at the top and bottom while it does, and let past the sides, and past the top and bottom while it fits, bypaintOverflow's band. -
ScrollView
Wrapschild, scrolling only on the axes enabled here.
-
-
Method Details
-
setScrollbarPolicy
Sets the scrollbar visibility policy (defaultScrollBar.Policy.AUTO). -
setBarLayout
Sets whether the bars float over the content or reserve a strip of their own (defaultScrollGutters.Layout.OVERLAY). -
barLayout
Whether the scrollbars overlay the content or reserve a gutter. -
setBarInsets
Sets the bars' tracks in from the ends of their sides, for content that scrolls under something laid over it: a bar of glass across the top that the content passes beneath keeps its own thumb out from under the bar with the bar's height as the top inset. The vertical bar's track startstopdown and endsbottomup; the horizontal bar's startsleftin and endsrightin, the two swapping sides when the view reads right to left, as aPadding's do. The thumb keeps its proportion of the shorter track. The content, its viewport and the scrolling are unchanged.- Parameters:
insets- how far each track is set in;Insets.NONE(the default) for none- Returns:
- this view
-
barInsets
- Returns:
- how far each bar's track is set in from the ends of its side
-
paintOverflow
Declares how far the content may paint past the viewport across an axis this view does not scroll, or one its content fits, in logical points, in place of the reach the theme gives a focus ring. UI thread only.What it is for. On an axis it scrolls and the content overflows, this view clips the content exactly at the viewport, because there the content moves under the edge and what lies past it is out of sight. On an axis it does not scroll, or one the content fits, nothing can move out of sight, and a clip there cut only what a control draws outside its own box at the viewport's edge: the focus ring of a checkbox flush with the side of a view that scrolls vertically, or with its bottom while nothing overflows, a cursor centred on the edge of a colour plane. So across such an axis the content may paint this far past the viewport, on both sides, and is clipped there. A view whose content overflows it both ways lets nothing past; one whose content fits it lets the band past on all four sides, whatever it scrolls. The layout that makes the content overflow an axis or fit it again repaints the wider of the two clips, so that what the content drew in a band that closed is wiped and what it draws in one that opened is drawn at once.
By default the band is the reach of the focus ring in the theme in effect for this view,
Theme.focusRingReach(), the number every layout declares for its children, so a focused control flush with the view's side keeps its whole ring wherever the view has that much room round it. Declare more when the content draws farther and there is room for it, the padding of a card round the view for instance, and 0 for a view that must clip at its viewport on every side.paintOverflowFromTheme()goes back to the theme's number.Three things the band never reaches. A strip reserved for a bar (
ScrollGutters.Layout.RESERVED) is not painted into: on the side the strip holds, the content stops at the viewport whatever this says. The bars stay clipped to this view's box. And the band is this view's allowance, not its ancestors': a clip above the view still cuts whatever reaches past it.It is also what this view declares to partial rendering as painting outside its box, through
paintOutset(), which is one number for both axes. So the view declares it only while an axis is left unclipped, and a view whose content overflows it both ways declares nothing; on an axis the view clips the number over-declares by the band, which costs that many points of repaint above and below the view and never a stale pixel. The clip every descendant's damage is clamped to is the clip the content is painted in, band included, so a ring hanging into the band is repainted there when it changes.A declaration already in force changes nothing and repaints nothing. A new one repaints this view and the wider of the band it leaves and the band it takes, so that what the content drew in a band that narrowed is wiped, and what it draws in one that widened is drawn at once.
- Parameters:
points- how far the content may paint past each side of the viewport across an axis this view does not scroll or its content fits, in logical points- Returns:
- this view
- Throws:
IllegalArgumentException- when negative,NaNor infinite
-
paintOverflowFromTheme
Goes back to letting the content paint past the viewport by the reach of the theme's focus ring across an axis this view does not scroll or its content fits, which is the default; seepaintOverflow(float). Repaints nothing when that is already the declaration. UI thread only.- Returns:
- this view
-
viewportWidth
public float viewportWidth()The width content is actually shown in: the box, less any reserved gutter. Every offset, clamp and reveal is measured against this rather than againstWidget.width(), or a reserved bar would hide the last column it exists to keep clear. -
viewportHeight
public float viewportHeight()Visible height in logical points, excluding anything the content overflows by. -
offsetX
public float offsetX()Horizontal scroll position in logical points,0at the leading edge: the left edge in a left-to-right subtree and the right edge in a right-to-left one. The range is[0, maxOffsetX()]in both, so "scrolled to the start" is0either way. -
offsetY
public float offsetY()Vertical scroll position in logical points,0at the top. -
maxOffsetX
public float maxOffsetX()Largest validoffsetX();0when the content fits. -
maxOffsetY
public float maxOffsetY()Largest validoffsetY();0when the content fits. -
scrollBy
public void scrollBy(float dx, float dy) Scrolls by a delta in logical points (clamped). -
revealRect
public void revealRect(float x, float y, float rectWidth, float rectHeight) Scrolls the minimum so the rect (in viewport coordinates) becomes visible.- Specified by:
revealRectin interfaceScrollable
-
scrollTo
public void scrollTo(float newOffsetX, float newOffsetY) Scrolls to an absolute offset (clamped to the content). A caller's write, announced asVALUE/CODE; the wheel, the keys and the bars announceUSERthrough the same seam, and a layout that clamped the offset anADJUSTMENT. UI thread only.Called before the first layout, as a view built already scrolled is, the offset is kept and the first layout clamps it to the content it measures: the first frame shows the view where it was asked to be, never at the top first.
-
onMeasure
Description copied from class:WidgetReports the size this widget wants withinconstraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.Resolve the
ControlSizeand theLayoutDirectiononce each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, andI18n.locale()answers it wherever text is resolved.- Specified by:
onMeasurein classWidget<ScrollView>
-
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child).- Overrides:
onLayoutin classWidget<ScrollView>
-
onAccessibility
One scroll pane, whether or not anything overflows today, carrying where the viewport sits in the content and how much of it shows on each axis.The role is unconditional. The pane is the viewport whose clip decides what inside it is on screen at all, and a node that appeared when the content grew and vanished when it shrank would raise a structure change and a destroyed node on every resize; "nothing to scroll" is carried by the facet's two booleans instead. Without the role the pane declares nothing, is never focusable and paints nothing of its own, so the walk deletes it and hoists its content into whatever sits above it, which takes the scroll pattern out of a scene that is mostly scrollable.
The facet is six primitives read from this widget's own numbers, so describing it allocates nothing and needs no witness. The percent is
offset / maxOffsetwith the offset as this widget publishes it: zero at the leading edge in both directions, the range a positive magnitude either way, which is the facet's own "zero at the start" and is published unflipped under a right-to-left layout by decision, not omission; the mirroring lives in where the content is placed and never in the offset. The view size divides the viewport by the child and not by the content's measured size, because the layout gives the child the larger of the two on a scrolling axis and exactly the viewport on a fixed one, so the fraction never exceeds one and is exactly one on an axis that does not scroll, with no case for it. The zero guards are not defensive: the difference between two trees compares these fields with!=, and a pane laid out at nothing, or described before its first layout, would otherwise publish a not-a-number that differs from itself on every damaged frame and copy the whole tree each time. Scrollable ismaxOffset > 0, the predicate the wheel and the Page keys use, and not the bar's half-point slop: what a reader is told can scroll is what a wheel notch would move.No name, no state and no action are declared here. The pane holds no string; the axis bits name the one axis of a single value and the pane has two; and the vocabulary has no scroll verb with an axis in it, so nothing is advertised that the pane cannot perform. A scroll republishes the tree through the same damage the repaint rides, so nothing here marks the node dirty by hand.
- Overrides:
onAccessibilityin classWidget<ScrollView>- Parameters:
a- the builder for this widget's node
-
clipsChildren
protected boolean clipsChildren()Description copied from class:WidgetWhetherWidget.paintChildren(limn.graphics.Canvas)clips its children to this widget's bounds (scroll views, list viewports, tab strips). Partial rendering uses it to clamp a descendant'sWidget.invalidate()damage to the visible region; a widget scrolled out of view damages nothing. Any override that clips inpaintChildrenshould also override this, and one whose clip is smaller than its box for some child should override the four clip accessors below too.- Overrides:
clipsChildrenin classWidget<ScrollView>
-
paintOutset
protected float paintOutset()The band across an axis this view does not scroll or its content fits, which its content may paint into past the box; nothing while the content overflows it both ways, when the viewport bounds everything. One number for both axes, so on an axis the view clips it over-declares by the band, which repaints a little more and never leaves a stale pixel. O(1), from two flags and the band in force.- Overrides:
paintOutsetin classWidget<ScrollView>
-
clipX
Description copied from class:WidgetThe left edge, in this widget's own coordinates, of the rectangleWidget.clipsChildren()clipschildto.The whole box by default. A widget that paints one child inside an inset -- a scroll pane whose reserved gutters hold its bars, clipping the content to the viewport and the bars to the box -- answers per child, so that
Widget.isShowing()and the damage clamp agree with what it paints: until it did, a descendant lying wholly inside the gutter was published on screen in the very rectangle the bar occupies. Consulted only whileclipsChildren()is true, and it must not allocate, because the showing test runs for every node of every accessible walk.- Overrides:
clipXin classWidget<ScrollView>- Parameters:
forChild- the direct child the clipped descendant is under- Returns:
- the clip rectangle's left edge
-
clipY
Description copied from class:WidgetThe top edge of the rectangle this widget clipschildto; seeWidget.clipX(Widget).- Overrides:
clipYin classWidget<ScrollView>- Parameters:
forChild- the direct child the clipped descendant is under- Returns:
- the clip rectangle's top edge
-
clipWidth
Description copied from class:WidgetThe width of the rectangle this widget clipschildto; seeWidget.clipX(Widget).- Overrides:
clipWidthin classWidget<ScrollView>- Parameters:
forChild- the direct child the clipped descendant is under- Returns:
- the clip rectangle's width
-
clipHeight
Description copied from class:WidgetThe height of the rectangle this widget clipschildto; seeWidget.clipX(Widget).- Overrides:
clipHeightin classWidget<ScrollView>- Parameters:
forChild- the direct child the clipped descendant is under- Returns:
- the clip rectangle's height
-
paintChildren
Description copied from class:WidgetChildren pass; override to clip (e.g. scroll views).Indexed rather than an enhanced
for, for the reasonScene's own overlay loop already carries: an iterator here is an allocation per widget per frame, and an idle window is the one place in this toolkit that is not allowed to allocate at all. A subtree of two hundred widgets was paying two hundred short-lived objects a frame to walk a list it can index.- Overrides:
paintChildrenin classWidget<ScrollView>
-
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<ScrollView>
-
onKeyEvent
Description copied from class:WidgetKey events (focused widget first, then ancestors).- Overrides:
onKeyEventin classWidget<ScrollView>
-