Class Expander

java.lang.Object
limn.scene.Widget<Expander>
limn.components.Expander

public final class Expander extends Widget<Expander>
A section that opens and closes: a header over content that slides out below it. The header holds an arrow that turns as the section opens, a title, an optional icon at its start and an optional summary at its end, which says what is inside while it is closed ("3 files", "Off").

It opens and closes by a click on the header, by Space or Enter while the header holds the keyboard, and from setExpanded(boolean). The content slides out over Theme.animExpand, and appears at once when the user asked the system for less motion. Once it is closed the content is hidden: it takes no room, no focus and no pointer, and a reader does not find it. While it slides shut it is still drawn, as it was and not greyed, but it answers nothing from the moment the close starts: a press on it reaches no control, Tab and an access letter pass it by, nothing inside can take the keyboard, and a reader finds its nodes but no verb on them acts. Closing it while the keyboard is inside moves the keyboard to the header as the close starts, which is where the user just was. The content's own enabled state is the application's and is never changed; opened again, it answers as before.

A reader hears the header as a button, expanded or collapsed, that controls the content; the summary is its description. An ExpanderGroup keeps at most one of several open.

Reading right to left the arrow, the icon and the title start from the right and the summary ends on the left, and a closed arrow points to the left, where reading goes.

  • Constructor Details

    • Expander

      public Expander(String title, Widget<?> content)
      A closed section with a fixed title; see the I18nString constructor for a localized one.
      Parameters:
      title - the header's title
      content - what opens below it
    • Expander

      public Expander(I18nString title, Widget<?> content)
      A closed section whose title follows the UI language.
      Parameters:
      title - the header's title
      content - what opens below it
  • Method Details

    • title

      public String title()
      Returns:
      the title, resolved in this widget's own language
    • setTitle

      public Expander setTitle(String newTitle)
      Sets a fixed title.
    • setTitle

      public Expander setTitle(I18nString newTitle)
      Sets a title that follows the UI language.
    • setIcon

      public Expander setIcon(@Nullable Icon newIcon)
      Sets the icon at the header's start, tinted to the title's colour and drawn as authored; null takes it away.
    • setIcon

      public Expander setIcon(@Nullable Icon newIcon, Icon.Mirroring mirroring)
      Sets the icon at the header's start, saying whether its drawing turns around when the interface reads right to left.
      Parameters:
      mirroring - Icon.Mirroring.NEVER unless the glyph means a direction
    • icon

      public @Nullable Icon icon()
      Returns:
      the icon at the header's start, or null
    • setSummary

      public Expander setSummary(@Nullable String newSummary)
      Sets the summary at the header's end, in a quieter colour: what is inside, said while it is closed. A reader hears it as the header's description. null takes it away.
    • setSummary

      public Expander setSummary(@Nullable I18nString newSummary)
      Sets a summary that follows the UI language, or takes it away with null.
    • clearSummary

      public Expander clearSummary()
      Takes the summary away, as setSummary(null) would: that call does not compile, because null fits more than one of the overloads.
      Returns:
      this expander
    • summary

      public @Nullable String summary()
      Returns:
      the summary, resolved in this widget's own language, or null
    • content

      public Widget<?> content()
      Returns:
      what opens below the header
    • setContent

      public Expander setContent(Widget<?> newContent)
      Puts other content below the header, open or closed as the section is.
      Parameters:
      newContent - what opens below the header
    • isExpanded

      public boolean isExpanded()
      Returns:
      whether it is open, or opening
    • setExpanded

      public Expander setExpanded(boolean value)
      Opens or closes it: a caller's write, so it announces EXPANDED/CODE and runs no handler. In a group, opening it closes the one that was open, which announces too. The same state again changes nothing and announces nothing. UI thread only.
    • onChange

      public Expander onChange(@Nullable Consumer<Boolean> listener)
      The application's response to the user opening or closing it: a click, Space or Enter on the header, or a reader's verb, with the new state. Never for setExpanded(boolean), nor for a group closing it because another opened from code; to hear every change whatever caused it, watch it instead.
      Parameters:
      listener - the handler, or null to clear the slot
      Returns:
      this expander
      Throws:
      IllegalStateException - if a handler is already registered
    • handleUserChange

      protected void handleUserChange(Change.Aspect aspect)
      Description copied from class: Widget
      Runs this widget's application handler for a change it has just announced. Called by Widget.runHandler(limn.scene.Change), and so by Widget.notifyChange(limn.scene.Change), only for a USER origin 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 box selectedIndex(), a text field text(). 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: a SearchField that overrode this for its own aspect and did not chain would silently delete TextField'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:
      handleUserChange in class Widget<Expander>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • onDetached

      protected void onDetached()
      A group does not hold a section that is out of the scene: a form rebuilt against the same group would otherwise leave it every section of every form before, each still counted and each holding its content.
      Overrides:
      onDetached in class Widget<Expander>
    • onAttached

      protected void onAttached()
      Description copied from class: Widget
      Called when this widget enters a scene (attached to the tree). Widget.scene() is the scene it just joined. Fires top-down, so a parent runs before its children. Default no-op.
      Overrides:
      onAttached in class Widget<Expander>
    • 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<Expander>
    • 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<Expander>
    • clipsChildren

      protected boolean clipsChildren()
      Description copied from class: Widget
      Whether Widget.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's Widget.invalidate() damage to the visible region; a widget scrolled out of view damages nothing. Any override that clips in paintChildren should also override this, and one whose clip is smaller than its box for some child should override the four clip accessors below too.
      Overrides:
      clipsChildren in class Widget<Expander>
    • paintChildren

      protected void paintChildren(Canvas canvas)
      Description copied from class: Widget
      Children pass; override to clip (e.g. scroll views).

      Indexed rather than an enhanced for, for the reason Scene'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:
      paintChildren in class Widget<Expander>
    • onAccessibilityChild

      protected void onAccessibilityChild(Widget<?> child, Accessibility a)
      Says of the content what only this section knows: that the header names it. A content that said nothing of itself becomes a group, so the link lands on a node; one that already has a role or a name keeps them.
      Overrides:
      onAccessibilityChild in class Widget<Expander>
      Parameters:
      child - the child being described
      a - the child's node