T
- a self-type pattern; when extending AbstractWidget with your own custom class, make sure to extend it like: public class MyWidget extends AbstractWidget<MyWidget> { ... }
. This lets the builder pattern-esque functions work nicely and be easily chainable without explicit casts.public abstract class AbstractWidget<T extends AbstractWidget<T>>
extends java.lang.Object
Widgets are the primary feature of the easel library. Widgets are typically arranged in a tree-like hierarchy, where the root of the tree receives the notification to perform an action (e.g. render()
or update()
) and passes that information down to all descendants.
Proper widget use is essentially a three-stage process: initialization, anchoring, and rendering. It is worth noting that interactive widgets (e.g. buttons) follow the same general pattern, but also need to consider an update step as well. The initialization step lets you construct the widget hierarchy, assign widgets to layout managers, and give each widget the information it would require to render "in a void". The anchoring step lets you position the widget on the screen (moving from the idea of being able to render "in a void" to "in the proper place on screen"). Finally, the rendering step simply ensures every widget in the tree renders to the position that was previously set by the anchoring step.
getContentWidth()
and have meaningful information about a widget. anchorAt
right before the render call. Most anchoring is aided by Layout widgets (e.g. GridLayout
, HorizontalLayout
, etc.). When using these layout helper classes, you just need to tell the layout where to position itself and it will handle its children automatically. Layouts are extremely convenient: use them! By the end of the anchoring step, widgets are in the proper place on screen - letting you use functions like getContentLeft()
.anchoredAt
family of methods each frame, unless the widget moves again. For "dynamic" widgets that move (e.g. tooltips that follow the mouse cursor), you typically want to anchor right before you render, e.g. widget.anchoredAt(x, y, AnchorPosition.CENTER).render(sb)
. Let's explore some example code to hopefully make these ideas a bit more concrete:
public class Foo implements RenderSubscriber {
private HorizontalLayout child;
public Foo() {
// Since this Foo widget can be considered the "top" of the widget hierarchy, it's okay to subscribe to the
// render method of BaseMod so that we get told when to render.
BaseMod.subscribe(this);
// Here we make a new horizontal layout containing three labels. Each label will be aligned along the bottom
// of the horizontal layout (which is 40 px tall) and spaced 20px apart.
//
// We also perform the anchoring step right here as well, since it's convenient and the widget won't move.
child = new HorizontalLayout(40, 20)
.withDefaultChildAnchor(AnchorPosition.CENTER_BOTTOM)
.withChild(new Label("Left"))
.withChild(new Label("Center"))
.withChild(new Label("Right"))
.anchoredCenteredOnScreen();
}
// This function gets called every frame thanks to our BaseMod event subscription.
public void receiveRender(SpriteBatch sb) {
// We're going to simply hide the actual game by painting a black rectangle which covers the entire screen
// to make it easier to see our widgets in action
sb.setColor(Color.BLACK);
sb.draw(ImageMaster.WHITE_SQUARE_IMG, 0, 0, Settings.WIDTH, Settings.HEIGHT);
// Then, this is all we need to do to render our centered layout. Easy.
child.render(sb);
}
}
Note how easy this was to get up and running - in just a few lines of code, we've built a pretty useful way of displaying information to the screen! The HorizontalLayout does most of the alignment fiddling for us (we just told it to render on the center of the screen, and it figured out where to put the labels accordingly). We followed our three step process: we built all the widgets in our constructor, anchored our root widget, and then our render loop made sure to render down the hierarchy.
Let's consider one more modification to this contrived example by letting the labels follow the mouse. First, we can take out the anchoredAt
call inside the constructor since we're going to anchor in our render loop instead:
child = new HorizontalLayout(40, 20)
.withDefaultChildAnchor(AnchorPosition.CENTER_BOTTOM)
.withChild(new Label("Left"))
.withChild(new Label("Center"))
.withChild(new Label("Right"));
Then, we can rewrite our render function a bit to anchor to the mouse position:
// ... (other code drawing a black rectangle to see the widgets better) ...
// Update the anchor to the mouse position each frame (this uses the clamping
// override to ensure we stay at least 20px away from the screen border)
child.anchoredAt(InputHelper.mX, InputHelper.mY, AnchorPosition.CENTER, 20)
.render(sb);
Now when we run this sample code, the labels will still be arranged nicely (aligned along the bottom with 20px spacing in between each) but the whole thing will follow the mouse cursor. We've even used the anchoredAtClamped(float, float, AnchorPosition, float)
override that ensures that nothing will render off-screen!
Building more complicated widget hierarchies never really gets more complex than this as long as you follow the 3 step process. Often it is convenient to make your own classes extend AbstractWidget
in order to take advantage of the various automatic layout managers; this approach is recommended once you find yourself having to deal with excessive manual placement of widgets, since the layouts were originally designed to make handling many widgets as painless as possible. As long as you stick to convention, it is pretty easy to add new functionality and compose widgets together in new and exciting ways.
Eventually, we hope to include some additional samples to study which provide more practical examples to follow. For now, please explore the various classes on this javadoc and take a look at the functions to see what's available.
Modifier and Type | Field and Description |
---|---|
protected boolean |
hasInteractivity |
protected com.megacrit.cardcrawl.helpers.Hitbox |
hb |
protected boolean |
isHovered |
protected boolean |
leftClickStarted |
protected java.util.function.Consumer<T> |
onLeftClick |
protected java.util.function.Consumer<T> |
onMouseEnter |
protected java.util.function.Consumer<T> |
onMouseLeave |
protected java.util.function.Consumer<T> |
onRightClick |
protected boolean |
rightClickStarted |
Constructor and Description |
---|
AbstractWidget() |
Modifier and Type | Method and Description |
---|---|
T |
anchoredAt(float x,
float y,
AnchorPosition anchorPosition)
Instantly moves the widget to a specific spot.
|
T |
anchoredAt(float x,
float y,
AnchorPosition anchorPosition,
InterpolationSpeed movementSpeed)
Moves the widget towards a specific spot.
|
T |
anchoredAtClamped(float x,
float y,
AnchorPosition anchorPosition,
float clampedBorder)
Instantly moves the widget towards a specific spot, ensuring that the target location is clamped inside the viewing area.
|
T |
anchoredAtClamped(float x,
float y,
AnchorPosition anchorPosition,
InterpolationSpeed movementSpeed,
float clampedBorder)
Moves the widget towards a specific spot, ensuring that the target location is clamped inside the viewing area.
|
T |
anchoredCenteredOnMouse()
Instantly moves the widget such that the center of the widget is centered on the mouse position.
|
T |
anchoredCenteredOnMouse(float offsetX,
float offsetY,
AnchorPosition anchorPosition)
Instantly moves the widget to the current mouse position.
|
T |
anchoredCenteredOnMouseClamped(float clampedBorder)
Instantly moves the widget such that the center of the widget is centered on the mouse position, but with an attempt to stay in bounds.
|
T |
anchoredCenteredOnMouseClamped(float offsetX,
float offsetY,
AnchorPosition anchorPosition,
float clampedBorder)
Instantly moves the widget to the current mouse position.
|
T |
anchoredCenteredOnScreen()
Instantly move the widget such that the widget's center point is centered on the screen's center point.
|
T |
anchoredCenteredOnScreen(InterpolationSpeed movementSpeed)
Moves the widget such that the widget's center point is centered on the screen's center point.
|
void |
cancelMovementQueue(boolean shouldTryAndResolveOneLastTime)
Cancels ALL queued movements (queued from anchoredAt calls with delays or slower interpolation speeds than INSTANT)
|
protected void |
cancelMovementQueueForAllChildren(boolean shouldTryAndResolveOneLastTime) |
T |
delayedTranslate(float deltaX,
float deltaY,
InterpolationSpeed movementSpeed,
long delayTimeMillis)
Translate the widget but delay the movement until a certain number of milliseconds have passed.
|
float |
getBottom()
The absolute bottom-most point of the widget, useful for layout managers.
|
float |
getContentBottom()
The bottom-most point of the inner content area, useful for internal widget rendering.
|
float |
getContentCenterX()
The horizontal center of the inner content area, useful for internal widget rendering.
|
float |
getContentCenterY()
The vertical center of the inner content area, useful for internal widget rendering.
|
abstract float |
getContentHeight()
The internal content height of the widget (excludes margins).
|
float |
getContentLeft()
The left-most point of the inner content area, useful for internal widget rendering.
|
float |
getContentRight()
The right-most point of the inner content area, useful for internal widget rendering.
|
float |
getContentTop()
The upper-most point of the inner content area, useful for internal widget rendering.
|
abstract float |
getContentWidth()
The internal content width of the widget (excludes margins).
|
float |
getHeight()
The total height of this widget (includes the content height and both bottom and top margins).
|
float |
getLeft()
The absolute left most point of the widget, useful for layout managers.
|
float |
getRight()
The absolute right-most point of the widget, useful for layout managers.
|
float |
getTop()
The absolute top-most point of the widget, useful for layout managers.
|
float |
getWidth()
The total width of this widget (includes the content width and both left and right margins).
|
void |
hide() |
protected void |
initializeInteractivity() |
boolean |
isMouseInBounds() |
boolean |
isMouseInContentBounds() |
protected void |
leftMouseClick() |
protected void |
mouseEnter() |
protected void |
mouseLeave() |
T |
onLeftClick(java.util.function.Consumer<T> onLeftClick) |
T |
onMouseEnter(java.util.function.Consumer<T> onMouseEnter) |
T |
onMouseLeave(java.util.function.Consumer<T> onMouseLeave) |
T |
onRightClick(java.util.function.Consumer<T> onRightClick) |
T |
refreshAnchor()
Force a refresh of the anchor position but keep the widget in the same exact spot.
|
void |
render(com.badlogic.gdx.graphics.g2d.SpriteBatch sb)
Renders this widget onto the SpriteBatch.
|
void |
renderTopLevel(com.badlogic.gdx.graphics.g2d.SpriteBatch sb)
Renders top level (especially tooltip) effects.
|
protected abstract void |
renderWidget(com.badlogic.gdx.graphics.g2d.SpriteBatch sb)
Custom widgets should implement this method for rendering.
|
protected void |
resolveMovementQueue()
Moves the widget to the target anchor position.
|
protected void |
rightMouseClick() |
protected void |
scaleHitboxToContent()
This should be called whenever the
getContentWidth() or getContentHeight() changes. |
void |
setAllDelayedMovement(float deltaX,
float deltaY,
InterpolationSpeed movementSpeed,
long startingTimeMillis)
Don't call directly.
|
protected void |
setChildrenDelayedMovement(float deltaX,
float deltaY,
InterpolationSpeed movementSpeed,
long startingTimeMillis)
For use with
delayedTranslate(float, float, InterpolationSpeed, long) . |
void |
show() |
java.lang.String |
toString() |
T |
translate(float deltaX,
float deltaY,
InterpolationSpeed movementSpeed)
Translate the widget by a given amount.
|
void |
update()
Update this widget if it requires any logic updates each frame.
|
protected void |
updateInteractivity() |
protected void |
updateWidget()
Required for interactive components.
|
T |
withMargins(float all)
Sets all margins to the same value.
|
T |
withMargins(float horizontal,
float vertical)
Sets the left and right margins to
horizontal and the bottom and top margins to vertical . |
T |
withMargins(float left,
float right,
float bottom,
float top)
Sets each margin independently.
|
protected boolean hasInteractivity
protected com.megacrit.cardcrawl.helpers.Hitbox hb
protected boolean leftClickStarted
protected boolean rightClickStarted
protected boolean isHovered
protected java.util.function.Consumer<T extends AbstractWidget<T>> onLeftClick
protected java.util.function.Consumer<T extends AbstractWidget<T>> onRightClick
protected java.util.function.Consumer<T extends AbstractWidget<T>> onMouseEnter
protected java.util.function.Consumer<T extends AbstractWidget<T>> onMouseLeave
public abstract float getContentWidth()
getWidth()
,
getContentHeight()
public abstract float getContentHeight()
getHeight()
,
getContentWidth()
public T withMargins(float all)
all
- the value to set each margin, in pixelswithMargins(float, float, float, float)
public T withMargins(float horizontal, float vertical)
horizontal
and the bottom and top margins to vertical
.horizontal
- value to set left and right margins, in pixelsvertical
- value to set bottom and top margins, in pixelswithMargins(float, float, float, float)
public T withMargins(float left, float right, float bottom, float top)
Sets each margin independently. The margins refer to how much space is allotted to the left, right, above, and below the widget before rendering. Essentially, widgets contain two distinct bounding boxes: the inner content (see: getContentWidth()
and getContentHeight()
), and the full area (see: getWidth()
and getHeight()
). With margins set to zero (the default value), these two areas are the same (e.g. getContentWidth() == getWidth()
. As you grow the margins, the total width and height grows as well (but the inner content remains untouched).
The inner content dimensions are kept constant based on the widget itself - as it can be considered how much area the widget needs to render itself in its entirety. Thus, any render()
calls will just refer to the content locations and dimensions (e.g. getContentLeft()
), while any layout specific code will require the full dimensions including the margins to align things correctly.
left
- left margin, in pixelsright
- right margin, in pixelsbottom
- bottom margin, in pixelstop
- top margin, in pixelswithMargins(float, float)
,
withMargins(float)
public T anchoredAt(float x, float y, AnchorPosition anchorPosition, InterpolationSpeed movementSpeed)
Moves the widget towards a specific spot. The anchorPosition
defines the point on the widget that will be placed at the position (x, y)
.
For example, anchoredAt(100, 200, AnchorPosition.LEFT_BOTTOM, InterpolationSpeed.FAST)
will set the bottom-left corner of the widget to 100 pixels from the left side of the screen and 200 pixels from the bottom of the screen. The widget then renders up and to the right of this point since we anchored at an AnchorPosition.LEFT_BOTTOM
. As a second example, using AnchorPosition.CENTER
ensures that (x, y)
will be the center of the widget.
The interpolation speed movementSpeed
determines how quickly the widget moves to the target location. Using a speed other than InterpolationSpeed.INSTANT
makes the widget move towards the desired position over the next few frames in a smoothly animated manner. For convenience since instant moving is often the desired effect, see anchoredAt(float, float, AnchorPosition)
.
Note: you should always anchor at least once before rendering. For more dynamic widgets that move a lot (e.g. a tooltip dependent on the mouse cursor location using InputHelper.mX
and InputHelper.mY
), a general pattern is to call anchoredAt
right before rendering in your main render function, e.g.:
widget.anchoredAt(x, y, AnchorPosition.CENTER, InterpolationSpeed.INSTANT)
.render(sb);
x
- the x position in pixels from the left edge of the screeny
- the y position in pixels from the bottom edge of the screenanchorPosition
- what piece of the widget will be moved to (x, y)
movementSpeed
- how quickly the widget will move towards the desired positionanchoredAt(float, float, AnchorPosition)
,
anchoredAtClamped(float, float, AnchorPosition, float)
,
anchoredAtClamped(float, float, AnchorPosition, InterpolationSpeed, float)
public final T anchoredAt(float x, float y, AnchorPosition anchorPosition)
Instantly moves the widget to a specific spot. The anchorPosition
defines the point on the widget that will be placed at the position (x, y)
.
For example, anchoredAt(100, 200, AnchorPosition.LEFT_BOTTOM)
will set the bottom-left corner of the widget to 100 pixels from the left side of the screen and 200 pixels from the bottom of the screen. The widget then renders up and to the right of this point since we anchored at an AnchorPosition.LEFT_BOTTOM
. As a second example, using AnchorPosition.CENTER
ensures that (x, y)
will be the center of the widget.
Note: you should always anchor at least once before rendering. For more dynamic widgets that move a lot (e.g. a tooltip dependent on the mouse cursor location using InputHelper.mX
and InputHelper.mY
), a general pattern is to call anchoredAt
right before rendering in your main render function, e.g.:
widget.anchoredAt(x, y, AnchorPosition.CENTER)
.render(sb);
x
- the x position in pixels from the left edge of the screeny
- the y position in pixels from the bottom edge of the screenanchorPosition
- what piece of the widget will be moved to (x, y)
anchoredAt(float, float, AnchorPosition, InterpolationSpeed)
,
anchoredAtClamped(float, float, AnchorPosition, float)
,
anchoredAtClamped(float, float, AnchorPosition, InterpolationSpeed, float)
public final T anchoredAtClamped(float x, float y, AnchorPosition anchorPosition, InterpolationSpeed movementSpeed, float clampedBorder)
Moves the widget towards a specific spot, ensuring that the target location is clamped inside the viewing area. The anchorPosition
defines the point on the widget that will (attempt) to be placed at the position (x, y)
, unless putting it there would render part of the widget outside the screen.
This method uses the clampedBorder
parameter to restrict the positioning such that an edge of the widget's final bounding box is at least clampedBorder
pixels away from the absolute edge of the screen. The most common reason to use this clamping variant of the anchoredAt
family is to ensure tooltips (or other widgets) can attach to the mouse position but remain entirely on screen. If you do not clamp, portions of your tooltip may be unreadable and rendered offscreen. The clampedBorder
should be non-negative.
If the widget is larger than the screen in some dimension, (e.g. the widget is wider than the total width of the screen minus twice how much border spacing is allocated with clampedBorder
), the behavior of this method should be considered undefined.
The interpolation speed movementSpeed
determines how quickly the widget moves to the target location. Using a speed other than InterpolationSpeed.INSTANT
makes the widget move towards the desired position over the next few frames in a smoothly animated manner. For convenience since instant moving is often the desired effect, see anchoredAtClamped(float, float, AnchorPosition, float)
.
Note: you should always anchor at least once before rendering. For more dynamic widgets that move a lot (e.g. a tooltip dependent on the mouse cursor location using InputHelper.mX
and InputHelper.mY
), a general pattern is to call anchoredAt
right before rendering in your main render function, e.g.:
widget.anchoredAt(x, y, AnchorPosition.CENTER, InterpolationSpeed.INSTANT, 20)
.render(sb);
x
- the x position in pixels from the left edge of the screeny
- the y position in pixels from the bottom edge of the screenanchorPosition
- what piece of the widget will be moved to (x, y)
movementSpeed
- how quickly the widget will move towards the desired positionclampedBorder
- how close this widget is allowed to get to the outer edge of the screenanchoredAtClamped(float, float, AnchorPosition, float)
,
anchoredAt(float, float, AnchorPosition)
public final T anchoredAtClamped(float x, float y, AnchorPosition anchorPosition, float clampedBorder)
Instantly moves the widget towards a specific spot, ensuring that the target location is clamped inside the viewing area. The anchorPosition
defines the point on the widget that will (attempt) to be placed at the position (x, y)
, unless putting it there would render part of the widget outside the screen.
This method uses the clampedBorder
parameter to restrict the positioning such that an edge of the widget's final bounding box is at least clampedBorder
pixels away from the absolute edge of the screen. The most common reason to use this clamping variant of the anchoredAt
family is to ensure tooltips (or other widgets) can attach to the mouse position but remain entirely on screen. If you do not clamp, portions of your tooltip may be unreadable and rendered offscreen. The clampedBorder
should be non-negative.
If the widget is larger than the screen in some dimension, (e.g. the widget is wider than the total width of the screen minus twice how much border spacing is allocated with clampedBorder
), the behavior of this method should be considered undefined.
Note: you should always anchor at least once before rendering. For more dynamic widgets that move a lot (e.g. a tooltip dependent on the mouse cursor location using InputHelper.mX
and InputHelper.mY
), a general pattern is to call anchoredAt
right before rendering in your main render function, e.g.:
widget.anchoredAt(x, y, AnchorPosition.CENTER, 20)
.render(sb);
x
- the x position in pixels from the left edge of the screeny
- the y position in pixels from the bottom edge of the screenanchorPosition
- what piece of the widget will be moved to (x, y)
clampedBorder
- how close an edge of the widget is allowed to get to an edge of the screen (in pixels)anchoredAtClamped(float, float, AnchorPosition, float)
,
anchoredAt(float, float, AnchorPosition)
public final T anchoredCenteredOnScreen()
anchoredCenteredOnScreen(InterpolationSpeed)
,
anchoredAt(float, float, AnchorPosition)
public final T anchoredCenteredOnScreen(InterpolationSpeed movementSpeed)
movementSpeed
determines how quickly the move happens; if it is not InterpolationSpeed.INSTANT
, the move will smoothly animate over the next few frames.movementSpeed
- how quickly to move the widgetanchoredCenteredOnScreen()
,
anchoredAt(float, float, AnchorPosition, InterpolationSpeed)
public final T anchoredCenteredOnMouse()
anchoredCenteredOnMouse(float, float, AnchorPosition)
with no offset and AnchorPosition.CENTER
as the anchor.public final T anchoredCenteredOnMouseClamped(float clampedBorder)
anchoredCenteredOnMouseClamped(float, float, AnchorPosition, float)
with no offset and AnchorPosition.CENTER
as the anchor.clampedBorder
- how close the widget is allowed to get to the sides of the screenanchoredAtClamped(float, float, AnchorPosition, float)
public final T anchoredCenteredOnMouse(float offsetX, float offsetY, AnchorPosition anchorPosition)
AnchorPosition.LEFT_TOP
, the widget will have its top left corner 10 pixels to the right and 10 pixels down from the current mouse position.offsetX
- horizontal adjustment off the mouse position (positive values are towards the right of the screen)offsetY
- vertical adjustment off the mouse position (positive values are towards the top of the screen)anchorPosition
- which piece of the widget will be anchored to the mouse position + offsetspublic final T anchoredCenteredOnMouseClamped(float offsetX, float offsetY, AnchorPosition anchorPosition, float clampedBorder)
AnchorPosition.LEFT_TOP
, and a clampedBorder of 10, the widget will have its top left corner 10 pixels to the right and 10 pixels down from the current mouse position unless doing so would render the widget off screen.offsetX
- horizontal adjustment off the mouse position (positive values are towards the right of the screen)offsetY
- vertical adjustment off the mouse position (positive values are towards the top of the screen)anchorPosition
- which piece of the widget will be anchored to the mouse position + offsetsclampedBorder
- how close the widget is allowed to get to the sides of the screenpublic final T refreshAnchor()
Force a refresh of the anchor position but keep the widget in the same exact spot. This may be useful in a few extremely niche cases, though in typical use of the library this function should NOT be needed nor used. This can be used to recompute subtle anchor changes down the hierarchy as it simply calls anchoredAt() but in a way where the position is unchanged. Note that calling this function may invalidate any previous slower-moving anchor calls (i.e. those generated by anchor calls without InterpolationSpeed.INSTANT
). Friendly advice: don't structure any custom widgets in a way where this function is required if you can avoid it. As a reminder, calling any member of the anchoredAt family can be reasonably expensive and should be avoided if possible (i.e. only call anchoredAt once until the widget actually needs to move; hopefully the first time you've called anchoredAt everything will update properly). If you're using easel's built in widgets, the general hope is that calling this function on them won't be noticeable at all / won't do what you probably were wanting it to do. This function is mostly included as a "nuclear option" if some custom widget just wants a (lazy) way to refresh some sort of internal layout.
Note: Implementation-wise, this function is simply an alias for anchoredAt(getLeft(), getBottom(), AnchorPosition.LEFT_BOTTOM)
, and will traverse down the hierarchy as needed.
protected void setChildrenDelayedMovement(float deltaX, float deltaY, InterpolationSpeed movementSpeed, long startingTimeMillis)
delayedTranslate(float, float, InterpolationSpeed, long)
. Make sure any widget with children (e.g. containers, layouts, etc.) overrides this function and calls setAllDelayedMovement(float, float, InterpolationSpeed, long)
using the input to this function on all direct descendants. This is required if you want to be able to use a delayed anchoredAt command on custom widgets.deltaX
- how much movement horizontallydeltaY
- how much movement verticallymovementSpeed
- how fast the widget will move towards the target, once startingTimeMillis is reached (i.e. System.currentTimeMillis()
is greater than or equal to this starting time)startingTimeMillis
- a time generated by an offset of System.currentTimeMillis()
, determined by the original delayedTranslate(float, float, InterpolationSpeed, long)
function that starts this chainpublic final void setAllDelayedMovement(float deltaX, float deltaY, InterpolationSpeed movementSpeed, long startingTimeMillis)
setChildrenDelayedMovement(float, float, InterpolationSpeed, long)
as the operation called on all a widget's children in order to percolate the results of delayedTranslate(float, float, InterpolationSpeed, long)
down the widget hierarchy. Use delayedTranslate(float, float, InterpolationSpeed, long)
if you want to use the delayed movement functionality (this function is public to make it slightly easier for widgets with children to use stream iterators, but should be considered as if it was protected and not used directly).deltaX
- how much movement horizontallydeltaY
- how much movement verticallymovementSpeed
- how fast the widget will move towards the target, once startingTimeMillis is reached (i.e. System.currentTimeMillis()
is greater than or equal to this starting time)startingTimeMillis
- a time generated by an offset of System.currentTimeMillis()
, determined by the original delayedTranslate(float, float, InterpolationSpeed, long)
function that starts this chainpublic final void cancelMovementQueue(boolean shouldTryAndResolveOneLastTime)
shouldTryAndResolveOneLastTime
- if true, attempts to resolve the queue one last time (this will execute INSTANT movements that happen to be on the top of the queue, and any other move types that happen to be close enough to the finish to snap in)protected void cancelMovementQueueForAllChildren(boolean shouldTryAndResolveOneLastTime)
public final T delayedTranslate(float deltaX, float deltaY, InterpolationSpeed movementSpeed, long delayTimeMillis)
Translate the widget but delay the movement until a certain number of milliseconds have passed. Note that this is a more "advanced" anchoring function which lets you set a simple timer to aid in making more aesthetic movements (e.g. synchronizing widgets to move one after the other). This function will override any previous calls of itself if they're not completed by the time the timer expires (i.e you can only have one of these working at once, and the one that finishes first will clear the others). Custom widgets that have descendants of their own will need to override setChildrenDelayedMovement(float, float, InterpolationSpeed, long)
and call setAllDelayedMovement(float, float, InterpolationSpeed, long)
on all children in order for this function to work. By default, any widget included in this library that has descendants (e.g. layouts, containers, etc.) will already have this implemented and should be usable out of the box.
See anchoredAt(float, float, AnchorPosition, InterpolationSpeed)
for more details about anchoring.
deltaX
- how much to translate horizontally (positive values are to the right)deltaY
- how much to translate vertically (positive values are towards the top)movementSpeed
- how fast the widget will move towards the target, once startingTimeMillis is reached (i.e. System.currentTimeMillis()
is greater than or equal to this starting time)delayTimeMillis
- the number of milliseconds necessary to pass in the game loop before the move is startedtranslate(float, float, InterpolationSpeed)
,
anchoredAt(float, float, AnchorPosition, InterpolationSpeed)
public final T translate(float deltaX, float deltaY, InterpolationSpeed movementSpeed)
getLeft()
, getBottom()
) point to the right (or left if negative) a distance of deltaX
and up (or down) a distance of deltaY
. Translations depend on having anchored previously and are mostly included as convenience.deltaX
- how much to translate horizontally (positive values are to the right)deltaY
- how much to translate vertically (positive values are towards the top)movementSpeed
- how fast the widget will move towards the targetdelayedTranslate(float, float, InterpolationSpeed, long)
,
anchoredAt(float, float, AnchorPosition, InterpolationSpeed)
public float getWidth()
getContentWidth()
,
getHeight()
public float getHeight()
getContentHeight()
,
getWidth()
public float getContentLeft()
getLeft()
public float getContentRight()
getRight()
public float getContentBottom()
getBottom()
public float getContentTop()
getTop()
public float getContentCenterX()
getLeft() + marginLeft + 0.5f * getContentWidth()
. Undefined behavior if used before anchoring at least once.getContentCenterY()
public float getContentCenterY()
getBottom() + marginBottom + 0.5f * getContentHeight()
. Undefined behavior if used before anchoring at least once.getContentCenterX()
public float getLeft()
getContentLeft()
public float getBottom()
getContentBottom()
public float getTop()
getContentTop()
public float getRight()
getContentRight()
protected void resolveMovementQueue()
anchorAt
is called with an InterpolationSpeed
other than InterpolationSpeed.INSTANT
.public final void render(com.badlogic.gdx.graphics.g2d.SpriteBatch sb)
anchoredAt(float, float, AnchorPosition)
. If this widget changes the SpriteBatch parameters at all mid render (e.g. adding a shader, calling a sb.end(), etc.), these changes will be reset by the end of the render call so that the SpriteBatch has the same settings at the end as it did when entering this function. Container widgets (e.g. VerticalLayout
etc.) will render all children that they manage.sb
- the SpriteBatch to render this widget uponpublic void renderTopLevel(com.badlogic.gdx.graphics.g2d.SpriteBatch sb)
Renders top level (especially tooltip) effects. Like render(SpriteBatch)
, but deliberately delayed. If any of your widgets require custom (Widget-based, not base-game TipHelper based) tooltips or other specific rendering that needs to go on top of all other widgets in the hierarchy, you'll need to set your root node to call this after its main render(SpriteBatch)
completes.
If you are hooking into BaseMod's RenderSubscriber
for your base class, you can simply call this after your main call to rootWidget.render(spriteBatch)
. All layout managers and other container widgets will extend the calls down the tree automatically (much like how render()
works), meaning you only need to call this at the highest level of your widget hierarchy.
public void receiveRender(SpriteBatch spriteBatch) {
// Our normal render call will trickle down the hierarchy in a breadth-first manner, rendering every descendant widget.
baseWidget.render(spriteBatch);
// Call this after the above so that we can support custom tooltips (if desired); also trickles down the tree
baseWidget.renderTopLevel(spriteBatch);
}
Note that this is only required if you are using custom widgets that need to be rendered last (on top of everything). If you are sticking to base game style tooltips (e.g. using something like the TipHelper
statics), this is NOT required. However, if you do end up needing to draw on top of all widgets in your hierarchy, this is a solid way to do it as it follows the same breadth-first visit order but occurs after all the regular rendering takes place.
sb
- the SpriteBatch to render onrender(SpriteBatch)
protected abstract void renderWidget(com.badlogic.gdx.graphics.g2d.SpriteBatch sb)
Custom widgets should implement this method for rendering. Use the inner content positions (e.g. getContentLeft()
, getContentWidth()
, etc.) to determine any position information necessary for rendering at a specific location. If the library is used as intended, these content locations should be accurate to where the widget needs to be rendered, as they reflect the most up to date location set by an anchoredAt call (this automatically will be interpolated if the anchorAt move is set to occur over several frames).
Note: you NEED to revert any changes you make to the SpriteBatch (e.g. setting a shader, changing the perspective matrix, etc.) by the time this function returns, as the SpriteBatch will be reused for rendering other widgets which will not expect those changes. You also don't technically need to render to this particular SpriteBatch (e.g. you can render to your own batch if you know what you're doing), as long as you follow the general intent of this function to render the widget.
sb
- the SpriteBatch the widget should be rendered onrenderTopLevel(SpriteBatch)
protected void scaleHitboxToContent()
getContentWidth()
or getContentHeight()
changes.protected void initializeInteractivity()
public final void update()
VerticalLayout
) will pass updates to all their children. The top-most widget in the hierarchy can subscribe to BaseMod's post update subscriber (or via some other SpirePatch), but everything else lower down should NOT subscribe and instead just receive their update notifications from their parent widget. The update() function for non-interactive widgets with no children is essentially a NO-OP.
As this is called once per frame - for more complicated interactive widgets, you should try and avoid recomputing expensive things here unless absolutely needed. For many scenarios, it is better for custom widgets to compute information at one designated time and cache the results to be displayed later, instead of re-computing it again and again.protected void mouseEnter()
protected void mouseLeave()
protected void leftMouseClick()
protected void rightMouseClick()
protected void updateInteractivity()
public boolean isMouseInContentBounds()
getContentLeft()
and getContentRight()
etc.)public boolean isMouseInBounds()
getLeft()
and getRight()
etc.)protected void updateWidget()
update()
on all children. For widgets that require some sort of updates each frame, you can do so here.public void show()
public void hide()
public java.lang.String toString()
toString
in class java.lang.Object