Class Transform

java.lang.Object
limn.scene.Widget<Transform>
limn.scene.layout.Transform

public final class Transform extends Widget<Transform>
Draws one child moved, scaled, rotated, skewed, flipped or turned in depth, without changing where layout put anything: a render transform. The child is drawn, hit by the pointer and published to a screen reader where the transform puts it, and its box, the container's box and every sibling's place stay what they are at rest. A child scaled to twice its size overlaps its neighbours; it does not push them.

A container, as Padding is for space round a child and Opacity for a fade: the cost is only where the container is, and the order of two effects on one child is the order their containers are nested in. To fade a moving child, new Opacity(new Transform(child)) reads as "fade this moving thing" and draws the same picture as the other order.

The values, each with its default at rest:

  • a translation, setTranslation(float, float), in points: x toward the end of the layout direction (right in a left-to-right subtree, left in a right-to-left one), y down; (0, 0);
  • a rotation, setRotation(float), in degrees clockwise on the screen, the same way in either direction, as a clock turns; 0;
  • a scale, setScale(float, float): a negative factor mirrors, and a factor of 0 collapses the child to a line or a point, which then draws nothing and is out of reach (below); (1, 1);
  • a general matrix, setMatrix(limn.math.Mat4), for a skew or any other map the other values do not name; the identity;
  • an origin, setOrigin(float, float): the point a rotation, a scale, the matrix, a turn in depth and a flip keep still, as fractions of the box measured from its start edge and its top, so that a scale that grows from the start grows toward the end in either direction; (0.5, 0.5).
And, for turning the child's plane out of the screen:
  • a turn in depth, setRotation3D(Vec3, float), about an axis through the origin; none;
  • a perspective, setPerspective(float): how far in front of the origin the eye is, so that the near edge of a turned child is drawn larger than the far one; none, infinitely far, by default, which draws a turn about the vertical axis as a narrowing;
  • whether the child is drawn while its back is turned to the viewer, setBackfaceVisible(boolean); it is.
In this order, for a point of the child: the matrix, the scale, the rotation and the turn in depth, each about the origin, then the perspective, seen from in front of the origin, then the translation of what is seen, so that a translation moves the turned child as it is and a rotation turns the scaled one. Another order is two containers nested.

Transparent to everything but where its child is drawn. It measures, lays out and aligns on a baseline as its child alone does, the child given its whole box; it paints nothing of its own, takes no focus, and has no node of its own in the accessible tree, which holds the child in its place. It does not clip: what the child draws past the box is seen there, and a transform that should be cut goes inside a container that clips. It is hit only where its child is drawn, inside its box or past it, so the place a child slid away from takes the pointer from what is beneath.

A child drawn as nothing is out of reach, as a hidden one is. Collapsed by a scale of 0 on either axis, turned exactly edge-on, or showing its back while back faces are hidden, the child and everything inside it are not showing: the Tab key, an access letter and the pointer pass them by, the focus, the hover and a press held there are taken out of them, with the events a widget hidden by Widget.setVisible(boolean) is sent, inside the set that does it, or once the frame of a motion that does it has stepped every motion, or once the layout pass that does it (a new box tipping a plane past edge-on) is over, and a screen reader is told they are hidden. A popover or a list open from a control in them closes, as an adjustment, before the focus is taken out, and none opens from one. They come back by themselves, in reach and published again, when the scale returns or the face turns back; the focus they lost is not given back. Nothing of the child's own state changes, and Widget.isVisible() still answers what it was told.

What it costs: nothing at rest, where the frame is the frame without it. Moved or scaled, the child is drawn straight into the window, moved and scaled, every clip inside it exact; a change repaints where the child was and where it is, and lays nothing out. A uniform scale keeps text crisp; a scale animation draws the glyphs again at each size it passes through. Rotated or skewed, the child is drawn upright into a picture of its own, at the size it is seen at, and the picture is laid at the angle: every clip inside stays an exact rectangle, a view scrolled inside still skips what scrolled away, the edges are smoothed over about a pixel, and text reads slightly softer, as text at an angle does. A frosted glass inside reads the window behind as it would upright. The picture costs what an Opacity container's fade costs a frame of the same size; it is made again whenever something inside repaints while the child is turned, and given back the frame after the child comes to rest upright. Turned in depth, the picture is laid in perspective and drawn at the size of its nearest edge, so it stays sharp there, and text far from the eye reads softer; on a computer with no graphics processor, where every pixel of a picture costs processor time, it is drawn at the child's own size instead, and the near edge reads a little softer too. Where the picture is seen at less than half its size, a page turned far back, its detail is averaged rather than left to flicker while it turns. What part of a child passes behind the eye is not drawn, and the rest is. A frosted glass inside a child seen in perspective reads only what the child draws beneath it, not the window. Something inside that draws in a blend mode, a tint in BlendMode.MULTIPLY, blends with the picture while the child is turned, and where the child drew nothing beneath it is drawn as itself; at rest, or only moved and scaled, there is no picture and it blends with what is beneath the container.

A watcher hears it as Change.Aspect.TRANSFORM with origin CODE on this container: every time a set changes a value, once for reset(), and twice for a motion, as it starts and as it arrives or is superseded. A screen reader is told where the child is when a value is set and when a motion arrives, and not on the frames between: its highlight jumps to where the child came to rest.

Every setter and motion is fluent and UI thread only, refuses NaN and the infinities with IllegalArgumentException, and is refused from inside a paint, which is re-run per pass and must not move what it paints.

  • Constructor Details

    • Transform

      public Transform(Widget<?> child)
      Wraps child at rest: drawn where layout puts it.
      Parameters:
      child - the widget this container draws moved, scaled or flipped
  • Method Details

    • setTranslation

      public Transform setTranslation(float x, float y)
      Moves the child by (x, y) points, at once. x runs toward the end of the layout direction, so a drawer that slides in from the end edge is the same line in either direction, and y runs down. A slide in flight is superseded: it stops where it is, announces its end, and its onArrive never runs; nor does that of one gone straight there that still waits.
      Parameters:
      x - toward the end, in points
      y - down, in points
      Returns:
      this
      Throws:
      IllegalArgumentException - if either is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • translationX

      public float translationX()
      Returns:
      the translation toward the end, in points
    • translationY

      public float translationY()
      Returns:
      the translation down, in points
    • setScale

      public Transform setScale(float s)
      Scales the child by s on both axes about the origin, at once.
      Parameters:
      s - the factor; negative mirrors, 0 collapses the child to a point
      Returns:
      this
      Throws:
      IllegalArgumentException - if it is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
      See Also:
    • setScale

      public Transform setScale(float sx, float sy)
      Scales the child by sx along x and sy along y about the origin, at once. A scale in flight is superseded. A uniform scale keeps text crisp; two different factors draw it softer while they last. A factor of 0 on either axis draws the child as nothing and takes the keyboard focus out of it, as hiding it would (see the class's note).
      Parameters:
      sx - the factor along x; negative mirrors, 0 collapses the child to a line
      sy - the factor along y
      Returns:
      this
      Throws:
      IllegalArgumentException - if either is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • scaleX

      public float scaleX()
      Returns:
      the scale along x
    • scaleY

      public float scaleY()
      Returns:
      the scale along y
    • setOrigin

      public Transform setOrigin(float fractionX, float fractionY)
      Sets the point a scale and a flip keep still, as fractions of the box: fractionX from the start edge (the left in a left-to-right subtree, the right in a right-to-left one), fractionY from the top. (0.5, 0.5), the middle, by default; (0, 0) grows the child from its start corner. A fraction outside 0 to 1 puts the point outside the box, which is allowed.
      Parameters:
      fractionX - from the start edge
      fractionY - from the top
      Returns:
      this
      Throws:
      IllegalArgumentException - if either is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • originX

      public float originX()
      Returns:
      the origin's fraction of the box from its start edge
    • originY

      public float originY()
      Returns:
      the origin's fraction of the box from its top
    • setRotation

      public Transform setRotation(float degrees)
      Turns the child by degrees clockwise on the screen about the origin, at once. Physical: a right-to-left subtree turns the same way, as a clock does. A rotation in flight is superseded. The child is drawn through a picture while it is turned (see the class's note on what it costs); a whole turn of 360 degrees draws it upright, through the picture still.
      Parameters:
      degrees - clockwise on the screen
      Returns:
      this
      Throws:
      IllegalArgumentException - if it is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • rotation

      public float rotation()
      Returns:
      the rotation, in degrees clockwise on the screen
    • setMatrix

      public Transform setMatrix(Mat4 matrix)
      Sets a general matrix the child is drawn through, about the origin, before the scale and the turns: a skew, or any map the other values do not name. The child lies in the plane z = 0, so a point (x, y) of it, taken from the origin, goes through the matrix as (x, y, 0, 1) and is drawn at its x and y divided by its w; the z row matters through a turn in depth and the perspective, which see the depth it gives, and a bottom row that changes w draws the child in a perspective of the matrix's own. Physical, as the rotation is. The identity is no matrix. Mat4.affine2D(float, float, float, float, float, float) makes one from the six numbers of a 2D affine map, and Mat4.perspectiveAt(float) a perspective.
      Parameters:
      matrix - the matrix
      Returns:
      this
      Throws:
      NullPointerException - if it is null
      IllegalArgumentException - if an entry is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • matrix

      public Mat4 matrix()
      Returns:
      the general matrix, Mat4.IDENTITY when none is set
    • setRotation3D

      public Transform setRotation3D(Vec3 axis, float degrees)
      Turns the child's plane by degrees about axis, through the origin, at once: out of the screen, so that one edge comes toward the viewer and the opposite one goes away. The axes are the canvas's: x to the right, y down, z out of the screen toward the viewer. A positive angle about z turns as setRotation(float) does, clockwise; about y the right edge goes away from the viewer and the left comes toward it; about x the bottom edge comes toward the viewer and the top goes away. Physical, as the rotation is: a right-to-left subtree turns the same way.

      With no perspective a turn about y narrows the child, and one past ninety degrees shows its back, mirrored. A turn of exactly ninety degrees out of the screen leaves the child edge-on: it draws nothing and is out of reach, as the class's note says of a child drawn as nothing. A whole turn is no turn.

      Parameters:
      axis - the axis, of any length but 0
      degrees - the angle, in degrees, the way the paragraph above says for each axis; about any other axis, as a quaternion of that axis and angle turns
      Returns:
      this
      Throws:
      NullPointerException - if axis is null
      IllegalArgumentException - if a component or the angle is NaN or infinite, or the axis has no length
      IllegalStateException - off the UI thread, or from inside a paint
    • setRotation3D

      public Transform setRotation3D(Quat turn)
      Turns the child's plane by turn, through the origin, at once, in the canvas's axes as setRotation3D(Vec3, float) names them: for a turn composed of several, or one kept from another rotation. Normalized, so a quaternion of any length but 0 names its turn; of the two quaternions that name one turn, rotation3D() answers the one whose w is not below 0. Quat.IDENTITY is no turn.
      Parameters:
      turn - the turn
      Returns:
      this
      Throws:
      NullPointerException - if it is null
      IllegalArgumentException - if a component is NaN or infinite, or all are 0
      IllegalStateException - off the UI thread, or from inside a paint
    • rotation3D

      public Quat rotation3D()
      Returns:
      the turn in depth, a unit quaternion, Quat.IDENTITY when there is none
    • setPerspective

      public Transform setPerspective(float distance)
      Looks at the child from distance points in front of the origin, at once: a turned child's near edge is drawn larger than its far one, as a card held up and turned is seen. Two to four times the larger side of the box is the usual look; nearer is more dramatic, farther flatter. The vanishing point is the origin, and the translation moves it with what it sees. The perspective changes nothing of a child whose plane faces the viewer squarely: only a turn in depth, a flip or a matrix that gives the plane depth is seen through it.

      Float.POSITIVE_INFINITY, the default, is no perspective. A part of the child that a turn brings past the eye is not drawn, and the rest is.

      Parameters:
      distance - how far the eye is, in points, above 0; infinite for none
      Returns:
      this
      Throws:
      IllegalArgumentException - if it is not above 0, or is NaN
      IllegalStateException - off the UI thread, or from inside a paint
    • perspective

      public float perspective()
      Returns:
      how far the eye is in front of the origin, in points; infinite for no perspective
    • setBackfaceVisible

      public Transform setBackfaceVisible(boolean visible)
      Sets whether the child is drawn, and hit, while its plane shows the viewer its back: turned past ninety degrees out of the screen. Drawn by default, mirrored as the back of a pane of glass is; hidden, a turned-over child draws nothing and is out of reach as a hidden child is (see the class's note): not showing, passed by the Tab key and the pointer, the focus taken out of it, published hidden, until it turns back. A child mirrored by a negative scale still faces the viewer and is drawn either way.

      This is how two faces show one at a time without a swap: a Stack of two containers with the same perspective and back faces hidden, the back turned half a turn further than the front, both turned together. At every angle exactly one face is drawn, is showing and takes the keyboard and the pointer, the one turned toward the viewer, but at exactly ninety degrees from flat, where both planes lie along the line of sight and neither is drawn: for that one angle neither face is showing or in reach. A focus held in the face that turns away is lost as it turns.

      Parameters:
      visible - whether a back face is drawn and hit
      Returns:
      this
      Throws:
      IllegalStateException - off the UI thread, or from inside a paint
    • isBackfaceVisible

      public boolean isBackfaceVisible()
      Returns:
      whether the child is drawn and hit while its back is turned to the viewer
    • reset

      public Transform reset()
      Puts every value back at rest, the origin included, and stops every motion in flight, as one change: a watcher hears it once, besides the end of each motion it superseded. The callbacks of a motion gone straight there that still wait never run either.
      Returns:
      this
      Throws:
      IllegalStateException - off the UI thread, or from inside a paint
    • slideTo

      public Transform slideTo(float x, float y)
      slideTo(float, float, double, Runnable) over the theme's window animation, the time the toolkit's own popups and dialogs move in, with nothing run at the end.
      Parameters:
      x - toward the end, in points
      y - down, in points
      Returns:
      this
      Throws:
      IllegalArgumentException - if either is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • slideTo

      public Transform slideTo(float x, float y, double seconds, @Nullable Runnable onArrive)
      Moves the translation to (x, y) over seconds, eased as the toolkit eases a fade, one step a frame on the scene's wall-time ticker, which ticks only while something moves and which pausing the scene does not freeze. One ticker per container, whatever the number of motions asked for; a slide and a scale run together.

      Straight to the target, as one setTranslation(float, float), while the system asks for less motion, for no time, for the translation the container already has, or with no frames to step it: a container in no scene, or in a scene with no window. Its onArrive runs then after the seconds asked for, as a task Ui.postDelayed(java.lang.Runnable, long) runs, so that what follows it keeps the pace the call asked for; for no time, on the next turn of the UI thread's queue.

      onArrive never runs inside this call, whether the slide moves or goes straight there, so the next step of a sequence, or of motion that repeats, can be asked for in it. Such a chain keeps the pace its calls ask for, where the motions go straight there too, and the UI thread sleeps between its steps. It goes on, one step a period whether the container is shown or not, for as long as the code keeps asking, so repeating motion stops itself: when what it stands for is over, or when its container is no longer showing.

      A newer slide or a setTranslation(float, float) supersedes one in flight, or one gone straight there whose onArrive still waits: it stops where it is, and its onArrive never runs. A slide on a container that leaves its scene settles at its target as it leaves and runs its onArrive then, as does one that went straight there and still waits for its seconds (a flip's swap first, if it has not run). Both run once the container is out of the scene it left, so a motion they ask for follows the container: straight there when it is in no scene, or on the frames of the scene it went to, never on those of the window it left, and after the change that took it out has done all its own work. A callback that throws, on a frame or as its container leaves, is logged and reported as a throwing ticker is, and changes nothing for the motions beside it, the container's later ones, one it asked for before it threw, or the change.

      A watcher hears it twice, as Change.Aspect.TRANSFORM: when it starts, and when it arrives or is superseded. The frames between repaint without announcing and without telling a screen reader, which hears where the child is when it arrives.

      Parameters:
      x - toward the end, in points
      y - down, in points
      seconds - how long it takes, when the system does not ask for less motion
      onArrive - run once when it arrives, or after seconds when it goes straight there (on the next turn of the UI thread's queue for none); never inside this call, never when it is superseded; may be null
      Returns:
      this
      Throws:
      IllegalArgumentException - if x or y is NaN or infinite, or seconds is negative, NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • scaleTo

      public Transform scaleTo(float s)
      scaleTo(float, float, double, Runnable) to s on both axes over the theme's window animation, with nothing run at the end.
      Parameters:
      s - the factor to arrive at
      Returns:
      this
      Throws:
      IllegalArgumentException - if it is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • scaleTo

      public Transform scaleTo(float sx, float sy, double seconds, @Nullable Runnable onArrive)
      Moves the scale to (sx, sy) over seconds, by the rules slideTo states: eased, on the one ticker, straight there under less motion with onArrive after its seconds and never inside this call, superseded by a newer scale or a setScale(float), settled as the container leaves its scene, and heard twice. The glyphs of the text inside are drawn again at each size the scale passes through.

      A scale that arrives at 0 on either axis draws the child as nothing and puts it out of reach as a hidden child is (see the class's note): it loses the keyboard focus, which is not given back when the scale returns. A motion arrives exactly at its target, so a card turned over by hand, scaled to 0 across, swapped and scaled back, always loses the focus it held; flipTo turns a card over and keeps it. A motion that only passes 0 on its way, from 1 to −1, keeps it: its frames between do not land on 0.

      Parameters:
      sx - the factor along x to arrive at
      sy - the factor along y to arrive at
      seconds - how long it takes, when the system does not ask for less motion
      onArrive - run once when it arrives, or after seconds when it goes straight there (on the next turn of the UI thread's queue for none); never inside this call, never when it is superseded; may be null
      Returns:
      this
      Throws:
      IllegalArgumentException - if a factor is NaN or infinite, or seconds is negative, NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • rotateTo

      public Transform rotateTo(float degrees)
      rotateTo(float, double, Runnable) over the theme's window animation, with nothing run at the end.
      Parameters:
      degrees - the rotation to arrive at, clockwise on the screen
      Returns:
      this
      Throws:
      IllegalArgumentException - if it is NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • rotateTo

      public Transform rotateTo(float degrees, double seconds, @Nullable Runnable onArrive)
      Moves the rotation to degrees over seconds, by the rules slideTo states: eased, on the one ticker beside a slide and a scale, straight there under less motion with onArrive after its seconds and never inside this call, superseded by a newer rotation or a setRotation(float), settled as the container leaves its scene, and heard twice. It turns the short way or the long way as the numbers say: from 350 to 10 degrees is 340 degrees back, and to 370 is twenty forward.
      Parameters:
      degrees - the rotation to arrive at, clockwise on the screen
      seconds - how long it takes, when the system does not ask for less motion
      onArrive - run once when it arrives, or after seconds when it goes straight there (on the next turn of the UI thread's queue for none); never inside this call, never when it is superseded; may be null
      Returns:
      this
      Throws:
      IllegalArgumentException - if degrees is NaN or infinite, or seconds is negative, NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • flipTo

      public Transform flipTo(Transform.Flip direction, Runnable atEdge)
      flipTo(Flip, double, Runnable, Runnable) over the theme's window animation, with nothing run at the end.
      Parameters:
      direction - which way the child turns over
      atEdge - run with the child edge-on, where it swaps what it shows
      Returns:
      this
      Throws:
      NullPointerException - if either is null
      IllegalStateException - off the UI thread, or from inside a paint
    • flipTo

      public Transform flipTo(Transform.Flip direction, double seconds, Runnable atEdge, @Nullable Runnable onArrive)
      Turns the child over and back over seconds: from flat to edge-on about the axis direction names, where it runs atEdge, and from edge-on on the other side back to flat, so that what atEdge put in the child arrives the right way round. The place to hide one face and show the other: a card's front and back are two children of a Stack inside this container, and atEdge swaps which is visible.

      Drawn flat while no perspective is set: the child narrows to a line about the origin and widens back, which is cheap and needs nothing but a scale; text is softer while it turns and crisp again at the end. With a perspective it turns in depth, the edge coming over growing toward the viewer, through a picture while it turns. Either way it turns the child as it appears, after its own turn in depth and rotation, and leaves every value as it found it. A frame that lands exactly edge-on draws the child as nothing, which puts it out of reach for that frame (see the class's note); the face atEdge hides loses the focus as Widget.setVisible(boolean) takes it from any widget.

      By the rules of slideTo: eased over the whole turn, fastest edge-on; on the one ticker beside a slide and a scale; superseded by a newer flip, which turns on from wherever this one had got to and runs its own atEdge, or by reset(); settled as the container leaves its scene, atEdge first if the edge was not reached; heard twice. While the system asks for less motion, for no time, or with no frames to step it, there is no turn at all: atEdge runs on the next turn of the UI thread's queue, so the content changes at once and with no transition, and onArrive after seconds (with the swap, for none). Two flips asked for some way apart therefore swap twice, as they do when the first is past its edge as the second comes. A newer flip or a reset() before the swap supersedes both; after it, the swap stays made and only the waiting onArrive is dropped; and a flip that its own atEdge supersedes does not arrive. Neither runs inside this call, in any case.

      Parameters:
      direction - which way the child turns over
      seconds - how long the whole turn takes, when the system does not ask for less motion
      atEdge - run once, with the child edge-on, or on the next turn with no turn at all; never inside this call
      onArrive - run once when the child is flat again, or after seconds with no turn at all; never inside this call, never when it is superseded; may be null
      Returns:
      this
      Throws:
      NullPointerException - if direction or atEdge is null
      IllegalArgumentException - if seconds is negative, NaN or infinite
      IllegalStateException - off the UI thread, or from inside a paint
    • paintOutset

      protected float paintOutset()
      How far the child, with its own reach, is drawn past this box: its own declared reach at rest, as an Opacity container declares, and where the transform puts it otherwise, a drawer slid past the box or a card scaled up, so that a repaint of that band reaches it. Computed from the child's corners on asking, which is constant-time.
      Overrides:
      paintOutset in class Widget<Transform>
    • 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<Transform>
    • 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<Transform>
    • baselineOffset

      protected float baselineOffset()
      The child's: the container aligns on a baseline as the child alone does.
      Overrides:
      baselineOffset in class Widget<Transform>
    • onHitTest

      protected @Nullable Widget<?> onHitTest(float localX, float localY)
      The child where it is drawn, or nothing: never this container, which is only a place.
      Overrides:
      onHitTest in class Widget<Transform>
      Parameters:
      localX - the point's x in this widget's space
      localY - the point's y in this widget's space
      Returns:
      the widget the point lands on, or null