Class SplitPane
SplitPane split = SplitPane.horizontal(sidebar, editor).setRatio(0.25f);
split.setMinimums(180, 320); // neither pane below this
split.onRatioChange(app::rememberLayout);
The orientation names the axis the panes are arranged along, not the
divider: horizontal(limn.scene.Widget<?>, limn.scene.Widget<?>) puts them side by side, so the divider is the
vertical line between them.
The first pane takes the leading edge. Reading left to right that is the left, and reading right to left it is the right; the ratio, the minimums and the divider's position are all magnitudes along the divided axis and mean the same thing either way. A stacked split has no reading axis at all and is untouched by the layout direction.
The split is a ratio, not a width, so growing the window grows both panes. An application that wants a sidebar to keep its width while the other pane absorbs every new pixel re-sets the ratio from its own resize handling; there is no weight knob, because which pane absorbs a change is a per-layout decision rather than a per-splitter one.
setMinimums(float, float) keeps a pane from being dragged away entirely; the default
floor is a hit target's worth on each side. Pass 0 for a pane that is allowed
to collapse to nothing, such as a preview the user is meant to be able to dismiss.
The divider is thin to look at and thick to hit: it paints a hairline that thickens into the accent on hover, and catches a band a few points wider than the gutter, overlapping both panes. Focus, hover and drag are the same mark in three tints of the accent, each lighter than the one before.
The divider is not a tab stop unless asked; see
setDividerFocusable(boolean), which is what turns on the keyboard: arrows by a
visible step, by one point with Shift, Home and End to the minimum on either side.
Three panes are two splitters: nest one in the other.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumWhich axis the two panes are arranged along. -
Method Summary
Modifier and TypeMethodDescriptionprotected voidhandleUserChange(Change.Aspect aspect) Runs this widget's application handler for a change it has just announced.static SplitPanehorizontal(Widget<?> left, Widget<?> right) Two panes side by side, divided by a vertical line.booleanprotected voidSays what this widget is, for an assistive technology.protected @Nullable Widget<?> onHitTest(float localX, float localY) The divider first, so it wins where its grab band overlaps the panes, which is the whole point of the band; everywhere else the panes as ever.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.onRatioChange(@Nullable FloatConsumer listener) The application's response to the user moving the divider: a drag, a key, an assistive technology's step.Which axis the panes are arranged along.protected voidpaintChildren(Canvas canvas) The panes and then the divider, so its line is drawn over a ring that reaches the gutter.floatratio()setDividerFocusable(boolean focusable) Makes the divider a tab stop, off by default.setMinimums(float first, float second) Floors, in points, under which neither pane may be dragged.setRatio(float newRatio) Sets the share of the space given to the first pane,0..1.static SplitPaneTwo panes stacked, divided by a horizontal line.Methods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffset, baselineOffsetOf, caretRect, children, clipboard, clipHeight, clipsChildren, clipWidth, clipX, clipY, controlSize, cursor, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedBackward, focusArrivedByTraversal, 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, onKeyEvent, onMouseEvent, onPaint, onPaintOverlay, onPreedit, onSyntheticAction, overlayPassesPointer, paintOutset, 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
-
Method Details
-
onHitTest
The divider first, so it wins where its grab band overlaps the panes, which is the whole point of the band; everywhere else the panes as ever. -
paintChildren
The panes and then the divider, so its line is drawn over a ring that reaches the gutter.- Overrides:
paintChildrenin classWidget<SplitPane>
-
horizontal
Two panes side by side, divided by a vertical line. The first takes the leading edge, so the parameter names describe the default reading direction and not the screen: in a right-to-left layoutleftis the pane on the right. -
vertical
Two panes stacked, divided by a horizontal line. -
setRatio
Sets the share of the space given to the first pane,0..1. Clamped bysetMinimums(float, float)at layout, not here: the minimums are in points and the space is not known until there is a box to divide. Programmatic, so it does not fireonRatioChange. -
ratio
public float ratio()- Returns:
- the requested share of the first pane, before any minimum applies
-
setMinimums
Floors, in points, under which neither pane may be dragged.0lets that pane collapse entirely. When the box is too small for both floors the first one wins and the second pane takes what is left: a split that cannot honour both still has to lay out.firstis the leading pane's floor andsecondthe trailing one's, whichever way the layout reads; both are extents along the divided axis, so neither swaps when the direction does. -
onRatioChange
The application's response to the user moving the divider: a drag, a key, an assistive technology's step. Never forsetRatio(float); to hear every move whatever caused it, watch the split instead.- Parameters:
listener- the handler, ornullto clear the slot- Returns:
- this split
- Throws:
IllegalStateException- if a handler is already registered
-
handleUserChange
Description copied from class:WidgetRuns this widget's application handler for a change it has just announced. Called byWidget.runHandler(limn.scene.Change), and so byWidget.notifyChange(limn.scene.Change), only for aUSERorigin and after every watcher has run.A component overrides it once and switches on the aspect, reading its own accessors for the payload: a slider passes
value(), a combo boxselectedIndex(), a text fieldtext(). A component with no handler does not override it.An override that does not recognise the aspect must call
super, because a subclass of a component that has a handler inherits that component's dispatch through this method: aSearchFieldthat overrode this for its own aspect and did not chain would silently deleteTextField's. The ordering lives in the base class, this is the one link in it a subclass can break, and the contract test drives every subclass through its parent's gestures to catch one that does.- Overrides:
handleUserChangein classWidget<SplitPane>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
setDividerFocusable
Makes the divider a tab stop, off by default. A split is reached with the pointer, and a tab stop that only moves a boundary costs every keyboard user a stop on the way to the panes' own controls, so the keyboard support is here to be turned on by a layout that wants it, not to be paid for by one that does not.On, the divider takes focus by Tab and by a click on its band, shows the accent at its focus tint, and answers the arrows, Shift+arrow, Home and End. Off, nothing else changes: dragging, the cursor, the hover tint and the grab band are the pointer's, and none of them asks about focus.
The flag is the divider's rather than the split's, because the divider is what focus would land on; the split itself is a container and never a tab stop. UI thread only.
-
isDividerFocusable
public boolean isDividerFocusable()- Returns:
- whether the divider takes keyboard focus (
falseby default)
-
orientation
Which axis the panes are arranged along. -
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. -
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child). -
onAccessibility
Description copied from class:WidgetSays what this widget is, for an assistive technology.Called on the UI thread inside the publish step, with this widget's own locale in scope, and never from a paint. A widget that overrides nothing still gets a node with its bounds, its enabled, focusable, visible and showing states, its language, its children in tree order, the focus and scroll-into-view verbs when it is focusable, and its tooltip as a name when nothing else supplied one. What it does not get is a role: a widget that declares no role, no name, no description, no state, no verb and no facet is scaffolding, and the tree removes it and hoists its children in its place, so that a screen reader hears the controls rather than the boxes.
Nothing here may allocate. The builder's setters take a facet's fields rather than a facet, and a name is handed over as the localizable string this widget holds rather than as a resolved one, so that a frame which damaged this widget and changed nothing about it costs a comparison and no memory at all.
Accessibilitystates the two rules and what breaks when they are ignored.- Overrides:
onAccessibilityin classWidget<SplitPane>- Parameters:
a- the node being described; write into it, and never keep it
-