Class SplitPane

java.lang.Object
limn.scene.Widget
limn.components.SplitPane

public final class SplitPane extends Widget
Two panes and a divider the user drags to share the space between them.

 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.

  • Method Details

    • horizontal

      public static SplitPane horizontal(Widget left, Widget right)
      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 layout left is the pane on the right.
    • vertical

      public static SplitPane vertical(Widget top, Widget bottom)
      Two panes stacked, divided by a horizontal line.
    • setRatio

      public SplitPane setRatio(float newRatio)
      Sets the share of the space given to the first pane, 0..1. Clamped by setMinimums(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 fire onRatioChange.
    • ratio

      public float ratio()
      Returns:
      the requested share of the first pane, before any minimum applies
    • setMinimums

      public SplitPane setMinimums(float first, float second)
      Floors, in points, under which neither pane may be dragged. 0 lets 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.

      first is the leading pane's floor and second the trailing one's, whichever way the layout reads; both are extents along the divided axis, so neither swaps when the direction does.

    • onRatioChange

      public SplitPane onRatioChange(Consumer<Float> listener)
      Fires while the user drags or keys the divider, never on setRatio(float).
    • setDividerFocusable

      public SplitPane setDividerFocusable(boolean focusable)
      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 (false by default)
    • orientation

      public SplitPane.Orientation orientation()
      Which axis the panes are arranged along.
    • onMeasure

      protected Size onMeasure(Constraints constraints)
      Description copied from class: Widget
      Reports the size this widget wants within constraints. 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 ControlSize and the LayoutDirection once each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, and I18n.locale() answers it wherever text is resolved.

      Specified by:
      onMeasure in class Widget
    • onLayout

      protected void onLayout()
      Description copied from class: Widget
      Containers position children here (measure + Widget.layoutBox(float, float, float, float) per child).
      Overrides:
      onLayout in class Widget