UIForAnySwing.java
package swingtree;
import net.miginfocom.layout.AC;
import net.miginfocom.layout.CC;
import net.miginfocom.layout.ConstraintParser;
import net.miginfocom.layout.LC;
import net.miginfocom.swing.MigLayout;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import sprouts.Action;
import sprouts.Event;
import sprouts.*;
import swingtree.animation.AnimationState;
import swingtree.animation.Animator;
import swingtree.animation.LifeTime;
import swingtree.api.AnimatedStyler;
import swingtree.api.Peeker;
import swingtree.api.Styler;
import swingtree.api.UIVerifier;
import swingtree.api.mvvm.ViewSupplier;
import swingtree.input.Keyboard;
import swingtree.layout.AddConstraint;
import swingtree.layout.LayoutConstraint;
import swingtree.style.ComponentExtension;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* A generic SwingTree builder node designed as a basis for configuring any kind of {@link JComponent} instance.
* This is the most generic builder type and therefore abstract super-type for almost all other builders.
* This builder defines nested building for anything extending the {@link JComponent} class.
* <p>
* <b>Please take a look at the <a href="https://globaltcad.github.io/swing-tree/">living swing-tree documentation</a>
* where you can browse a large collection of examples demonstrating how to use the API of this class.</b>
* <br><br>
*
* @param <I> The concrete extension of the {@link UIForAnything}.
* @param <C> The type parameter for the component type wrapped by an instance of this class.
*/
public abstract class UIForAnySwing<I, C extends JComponent> extends UIForAnything<I, C, JComponent>
{
private static final Logger log = org.slf4j.LoggerFactory.getLogger(UI.class);
private final static String _TIMERS_KEY = "_swing-tree.timers";
@SuppressWarnings("ReferenceEquality")
protected final boolean _isUndefinedFont( Font font ) {
return font == UI.Font.UNDEFINED;
}
@SuppressWarnings("ReferenceEquality")
protected final boolean _isUndefinedColor( Color color ) {
return color == UI.Color.UNDEFINED;
}
/**
* This method exposes a concise way to bind a {@link Observable} (usually a sprouts.Event to the
* {@link JComponent#repaint()} method of the component wrapped by this {@link UI}!
* This means that the component will be repainted whenever the event is fired.
*
* @param event The event to which the repaint method of the component will be bound.
* @return The JComponent type which will be managed by this builder.
*/
public final I withRepaintOn( Observable event ) {
return _with( c -> event.subscribe( () -> _runInUI(c::repaint) ) )._this();
}
/**
* This method exposes a concise way to set an identifier for the component
* wrapped by this {@link UI}!
* In essence this is simply a delegate for the {@link JComponent#setName(String)} method
* to make it more expressive and widely recognized what is meant
* ("id" is shorter and makes more sense than "name" which could be confused with "title").
*
* @param id The identifier for this {@link JComponent} which will
* simply translate to {@link JComponent#setName(String)}
*
* @return The JComponent type which will be managed by this builder.
*/
public final I id( String id ) {
return _with( c -> ComponentExtension.from(c).setId(id) )._this();
}
/**
* This method exposes a concise way to set an enum based identifier for the component
* wrapped by this {@link UI}!
* In essence this is simply a delegate for the {@link JComponent#setName(String)} method
* to make it more expressive and widely recognized what is meant
* ("id" is shorter and makes more sense than "name" which could be confused with "title").
* <p>
* The enum identifier will be translated to a string using {@link Enum#name()}.
*
* @param id The enum identifier for this {@link JComponent} which will
* simply translate to {@link JComponent#setName(String)}
*
* @return The JComponent type which will be managed by this builder.
* @param <E> The enum type.
*/
public final <E extends Enum<E>> I id( E id ) {
Objects.requireNonNull(id);
return _with( c -> ComponentExtension.from(c).setId(id) )._this();
}
/**
* This method is part of the SwingTree style API, and it allows you to
* add this component to a style group.
* This is conceptually similar to CSS classes, with the difference that
* style groups can inherit from each other inside {@link swingtree.style.StyleSheet}s. <br>
* Here an example of how to define styles for a style group:
* <pre><code>
* new StyleSheet() {
* {@literal @}Override
* protected void build() {
* add(group("A").inherits("B", "C"), it -> it
* .backgroundColor(Color.RED)
* );
* add(group("B"), it -> it
* .borderWidth(12)
* );
* add(group("C"), it -> it
* .borderWidth(16)
* .borderColor(Color.YELLOW)
* );
* }
* }
* </code></pre>
* <br>
* The style sheet in the above example code can be applied to a component like so:
* <pre>{@code
* UI.use(new MyStyleSheet(), ()->
* UI.button("Click me").group("A")
* .onClick(it -> {...})
* );
* }</pre><br>
* <b>It is advised to use the {@link #group(Enum[])} method
* instead of this method, as the usage of enums for modelling
* group tags offers much better compile time type safety!</b>
*
* @param groupTags The names of the style groups to which this component should be added.
* @return This very instance, which enables builder-style method chaining.
*/
public final I group( String... groupTags ) {
return _with( c -> ComponentExtension.from(c).setStyleGroups(groupTags) )._this();
}
/**
* This method is part of the SwingTree style API, and it allows you to
* add this component to an enum based style group.
* This is conceptually similar to CSS classes, with the difference that
* style groups can inherit from each other inside {@link swingtree.style.StyleSheet}s. <br>
* Here an example of how to define styles for a style group:
* <pre><code>
* new StyleSheet() {
* {@literal @}Override
* protected void build() {
* add(group(MyGroups.A).inherits("B", "C"), it -> it
* .backgroundColor(Color.RED)
* );
* add(group(MyGroups.B), it -> it
* .borderWidth(12)
* );
* add(group(MyGroups.C), it -> it
* .borderWidth(16)
* .borderColor(Color.YELLOW)
* );
* }
* }
* </code></pre>
* <br>
* The style sheet in the above example code can be applied to a component like so:
* <pre>{@code
* UI.use(new MyStyleSheet(), ()->
* UI.button("Click me").group(MyGroup.A)
* .onClick(it -> {...})
* );
* }</pre>
*
* @param groupTags The enum based style group to which this component should be added.
* @return This very instance, which enables builder-style method chaining.
* @param <E> The enum type.
*/
@SafeVarargs
public final <E extends Enum<E>> I group( E... groupTags ) {
return _with( c -> ComponentExtension.from(c).setStyleGroups(groupTags) )._this();
}
/**
* Make the underlying {@link JComponent} type visible or invisible
* depending on the supplied boolean value.
*
* @param isVisible The truth value determining if the component should be visible or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isVisibleIf( boolean isVisible ) {
return _with( c -> c.setVisible(isVisible) )._this();
}
/**
* This is the inverse of {@link #isVisibleIf(boolean)}, and it is
* used to make the underlying {@link JComponent} type visible or invisible.
* <p>
* If the supplied boolean value is {@code true}, the component will be invisible. <br>
* If the supplied boolean value is {@code false}, the component will be visible.
*
* @param isVisible The truth value determining if the UI component should be visible or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isVisibleIfNot( boolean isVisible ) {
return _with( c -> c.setVisible(!isVisible) )._this();
}
/**
* Make the underlying {@link JComponent} type dynamically visible or invisible
* through the supplied {@link Val} property, which is automatically bound
* to the {@link JComponent#setVisible(boolean)} method of the underlying {@link JComponent} type.
* <p>
* This means that when the supplied {@link Val} property changes its value,
* then visibility of the underlying {@link JComponent} type will be updated accordingly.
* <p>
* <i>
* Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to
* send the property value to this view component.
* </i>
*
* @param isVisible The truth value determining if the UI component should be visible or not wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isVisibleIf( Val<Boolean> isVisible ) {
NullUtil.nullArgCheck(isVisible, "isVisible", Val.class);
NullUtil.nullPropertyCheck(isVisible, "isVisible", "Null is not allowed to model the visibility of a UI component!");
return _withOnShow( isVisible, (c, v) -> {
c.setVisible(v);
})
._with( c -> {
c.setVisible( isVisible.orElseThrow() );
})
._this();
}
/**
* This is the inverse of {@link #isVisibleIf(Val)}, and it is
* used to make the underlying {@link JComponent} type dynamically visible or invisible.
* <p>
* This means that when the supplied {@link Val} property changes its value,
* then visibility of the underlying {@link JComponent} type will be updated accordingly.
* <p>
* If the supplied {@link Val} property is {@code true}, the component will be invisible. <br>
* If the supplied {@link Val} property is {@code false}, the component will be visible.
* <p>
* <i>
* Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to
* send the property value to this view component.
* </i>
* @param isVisible The truth value determining if the UI component should be visible or not wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isVisibleIfNot( Val<Boolean> isVisible ) {
NullUtil.nullArgCheck(isVisible, "isVisible", Val.class);
NullUtil.nullPropertyCheck(isVisible, "isVisible", "Null is not allowed to model the visibility of a UI component! A boolean should only be true or false!");
return _withOnShow( isVisible, (c, v) -> {
c.setVisible(!v);
})
._with( c -> {
c.setVisible( !isVisible.orElseThrow() );
})
._this();
}
/**
* Make the underlying {@link JComponent} type dynamically visible or invisible
* based on the equality between the supplied enum value and enum property. <br>
* <p>
* This means that when the supplied {@link Val} property changes its value,
* and the new value is equal to the supplied enum value,
* then the underlying {@link JComponent} type will be visible,
* otherwise it will be invisible.
* <i>
* Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your
* view model to send the property value to this view component.
* </i>
*
* @param enumValue The enum value which, if equal to the supplied enum property, makes the UI component visible.
* @param enumProperty The enum property which, if equal to the supplied enum value, makes the UI component visible.
* @param <E> The enum type.
* @return This very instance, which enables builder-style method chaining.
*/
public final <E extends Enum<E>> I isVisibleIf( E enumValue, Val<E> enumProperty ) {
NullUtil.nullArgCheck(enumValue, "enumValue", Enum.class);
NullUtil.nullArgCheck(enumProperty, "enumProperty", Val.class);
NullUtil.nullPropertyCheck(enumProperty, "enumProperty", "Null is not allowed to model the visibility of a UI component!");
return _withOnShow( enumProperty, (c,v) -> {
c.setVisible( v == enumValue );
})
._with( c -> {
c.setVisible( enumValue == enumProperty.orElseThrow() );
})
._this();
}
/**
* This is the inverse of {@link #isVisibleIf(Enum, Val)}, and it is
* used to make the underlying {@link JComponent} type dynamically visible or invisible.
* <p>
* This means that when the supplied {@link Val} property changes its value,
* and the new value is equal to the supplied enum value,
* then the underlying {@link JComponent} type will be invisible,
* otherwise it will be visible.
* <i>
* Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your
* view model to send the property value to this view component.
* </i>
* @param enumValue The enum value which, if equal to the supplied enum property, makes the UI component invisible.
* @param enumProperty The enum property which, if equal to the supplied enum value, makes the UI component invisible.
* @param <E> The enum type for both the supplied enum value and enum property.
* @return This very instance, which enables builder-style method chaining.
*/
public final <E extends Enum<E>> I isVisibleIfNot( E enumValue, Val<E> enumProperty ) {
NullUtil.nullArgCheck(enumValue, "enumValue", Enum.class);
NullUtil.nullArgCheck(enumProperty, "enumProperty", Val.class);
NullUtil.nullPropertyCheck(enumProperty, "enumProperty", "Null is not allowed to model the visibility of a UI component!");
return _withOnShow( enumProperty, (c,v) -> {
c.setVisible( v != enumValue );
})
._with( c -> {
c.setVisible( enumValue != enumProperty.orElseThrow() );
})
._this();
}
/**
* Use this to enable or disable the wrapped UI component.
*
* @param isEnabled The truth value determining if the UI component should be enabled or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isEnabledIf( boolean isEnabled ) {
return _with( c -> _setEnabled(c, isEnabled) )._this();
}
/**
* This is the inverse of {@link #isEnabledIf(boolean)}.
* Use this to disable or enable the wrapped UI component.
*
* @param isEnabled The truth value determining if the UI component should be enabled or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isEnabledIfNot( boolean isEnabled ) {
return _with( c -> _setEnabled(c, !isEnabled) )._this();
}
/**
* Use this to dynamically enable or disable the wrapped UI component.
*
* @param isEnabled The truth value determining if the UI component should be enabled or not wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isEnabledIf( Val<Boolean> isEnabled ) {
NullUtil.nullArgCheck(isEnabled, "isEnabled", Val.class);
NullUtil.nullPropertyCheck(isEnabled, "isEnabled", "Null value for isEnabled is not allowed!");
return _withOnShow( isEnabled, (c,v) -> {
c.setEnabled(v);
})
._with( c -> {
_setEnabled(c, isEnabled.orElseThrow() );
})
._this();
}
/**
* This is the inverse of {@link #isEnabledIf(Val)}.
* Use this to dynamically disable or enable the wrapped UI component.
*
* @param isEnabled The truth value determining if the UI component should be enabled or not wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isEnabledIfNot( Val<Boolean> isEnabled ) {
NullUtil.nullArgCheck(isEnabled, "isEnabled", Val.class);
NullUtil.nullPropertyCheck(isEnabled, "isEnabled", "Null value for isEnabled is not allowed!");
return _withOnShow( isEnabled, (c,v) -> {
_setEnabled(c, !v);
})
._with( c -> {
_setEnabled(c, !isEnabled.orElseThrow() );
})
._this();
}
/**
* Use this to make the wrapped UI component dynamically enabled or disabled,
* based on the equality between the supplied enum value and enum property. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param enumValue The enum value which, if equal to the supplied enum property, makes the UI component enabled.
* @param enumProperty The enum property which, if equal to the supplied enum value, makes the UI component enabled.
* @param <E> The enum type for both the supplied enum value and enum property.
* @return This very instance, which enables builder-style method chaining.
*/
public final <E extends Enum<E>> I isEnabledIf( E enumValue, Val<E> enumProperty ) {
NullUtil.nullArgCheck(enumValue, "enumValue", Enum.class);
NullUtil.nullArgCheck(enumProperty, "enumProperty", Val.class);
NullUtil.nullPropertyCheck(enumProperty, "enumProperty", "The enumProperty may not have null values!");
return _withOnShow( enumProperty, (c,v) -> {
_setEnabled( c, v == enumValue );
})
._with( c -> {
_setEnabled(c, enumValue == enumProperty.orElseThrow() );
})
._this();
}
/**
* This is the inverse of {@link #isEnabledIf(Enum, Val)}.
* Use this to make the wrapped UI component dynamically disabled or enabled,
* based on the equality between the supplied enum value and enum property. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param enumValue The enum value which, if equal to the supplied enum property, makes the UI component disabled.
* @param enumProperty The enum property which, if equal to the supplied enum value, makes the UI component disabled.
* @param <E> The enum type for both the supplied enum value and enum property.
* @return This very instance, which enables builder-style method chaining.
*/
public final <E extends Enum<E>> I isEnabledIfNot( E enumValue, Val<E> enumProperty ) {
NullUtil.nullArgCheck(enumValue, "enumValue", Enum.class);
NullUtil.nullArgCheck(enumProperty, "enumProperty", Val.class);
NullUtil.nullPropertyCheck(enumProperty, "enumProperty", "The enumProperty may not have null values!");
return _withOnShow( enumProperty, (c,v) -> {
_setEnabled( c, v != enumValue );
})
._with( c -> {
_setEnabled(c, enumValue != enumProperty.orElseThrow() );
})
._this();
}
protected void _setEnabled(C c, boolean isEnabled ) { c.setEnabled( isEnabled ); }
/**
* Use this to make the wrapped UI component grab the input focus.
* @return This very instance, which enables builder-style method chaining.
*/
public final I makeFocused() {
return _with( c -> {
UI.runLater(() -> {
c.grabFocus();
// We do this later because in this point in time the UI is probably not
// yet fully built (swing-tree is using the builder-pattern).
});
})
._this();
}
/**
* Use this to make the wrapped UI component focusable.
* @param isFocusable The truth value determining if the UI component should be focusable or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isFocusableIf( boolean isFocusable ) {
return _with( c -> c.setFocusable(isFocusable) )._this();
}
/**
* Use this to dynamically make the wrapped UI component focusable.
* This is useful if you want to make a component focusable only if a certain condition is met.
* <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param isFocusable The truth value determining if the UI component should be focusable or not wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isFocusableIf( Val<Boolean> isFocusable ) {
NullUtil.nullArgCheck(isFocusable, "isFocusable", Val.class);
NullUtil.nullPropertyCheck(isFocusable, "isFocusable", "Null value for isFocusable is not allowed!");
return _withOnShow( isFocusable, (c,v) -> {
c.setFocusable(v);
})
._with( c -> {
c.setFocusable( isFocusable.orElseThrow() );
})
._this();
}
/**
* Use this to make the wrapped UI component focusable if a certain condition is not met.
* @param notFocusable The truth value determining if the UI component should be focusable or not.
* If {@code false}, the component will be focusable.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isFocusableIfNot( boolean notFocusable ) {
return _with( c -> c.setFocusable( !notFocusable ) )._this();
}
/**
* Use this to dynamically make the wrapped UI component focusable.
* This is useful if you want to make a component focusable only if a certain condition is met.
* <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param isFocusable The truth value determining if the UI component should be focusable or not, wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if the supplied {@code isFocusable} is {@code null}.
*/
public final I isFocusableIfNot( Val<Boolean> isFocusable ) {
NullUtil.nullArgCheck(isFocusable, "isFocusable", Val.class);
NullUtil.nullPropertyCheck(isFocusable, "isFocusable", "Null value for isFocusable is not allowed!");
return _withOnShow( isFocusable, (c,v) -> {
c.setFocusable( !v );
})
._with( c -> {
c.setFocusable( !isFocusable.orElseThrow() );
})
._this();
}
/**
* Use this to make the wrapped UI component dynamically focusable or non-focusable
* based on the equality between the supplied enum value and enum property. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param enumValue The enum value which, if equal to the supplied enum property, makes the UI component focusable.
* @param enumProperty The enum property which, if equal to the supplied enum value, makes the UI component focusable.
* @param <E> The enum type for both the supplied enum value and enum property.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if the supplied {@code enumValue} or {@code enumProperty} is {@code null}.
*/
public final <E extends Enum<E>> I isFocusableIf( E enumValue, Val<E> enumProperty ) {
NullUtil.nullArgCheck(enumValue, "enumValue", Enum.class);
NullUtil.nullArgCheck(enumProperty, "enumProperty", Val.class);
NullUtil.nullPropertyCheck(enumProperty, "enumProperty", "The enumProperty may not have null values!");
return _withOnShow( enumProperty, (c,v) -> {
c.setFocusable( v == enumValue );
})
._with( c -> {
c.setFocusable( enumValue == enumProperty.orElseThrow() );
})
._this();
}
/**
* This is the inverse of {@link #isFocusableIf(Enum, Val)}.
* Use this to make the wrapped UI component dynamically focusable or non-focusable
* based on the equality between the supplied enum value and enum property. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param enumValue The enum value which, if equal to the supplied enum property, makes the UI component non-focusable.
* @param enumProperty The enum property which, if equal to the supplied enum value, makes the UI component non-focusable.
* @param <E> The enum type for both the supplied enum value and enum property.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException if the supplied {@code enumValue} or {@code enumProperty} is {@code null}.
*/
public final <E extends Enum<E>> I isFocusableIfNot( E enumValue, Val<E> enumProperty ) {
NullUtil.nullArgCheck(enumValue, "enumValue", Enum.class);
NullUtil.nullArgCheck(enumProperty, "enumProperty", Val.class);
NullUtil.nullPropertyCheck(enumProperty, "enumProperty", "The enumProperty may not have null values!");
return _withOnShow( enumProperty, (c,v) -> {
c.setFocusable( v != enumValue );
})
._with( c -> {
c.setFocusable( enumValue != enumProperty.orElseThrow() );
})
._this();
}
/**
* Use this to make the wrapped UI component opaque.
* This is the inverse of {@link #makeNonOpaque()}.
*
* @return This very instance, which enables builder-style method chaining.
* @deprecated SwingTree considers the opaqueness a property which emerges from the
* style configuration of the component. Therefore, it is not recommended
* to set the opaqueness directly. Instead, use the {@link #withBackground(Color)}
* method to set the style of the component so that it becomes opaque.
*/
@Deprecated
public final I makeOpaque() {
return _with( c -> c.setOpaque( true ) )._this();
}
/**
* Use this to make the wrapped UI component transparent.
* This is the inverse of {@link #makeOpaque()}.
*
* @return This very instance, which enables builder-style method chaining.
* @deprecated Use {@link #withBackground(Color)} instead, by passing
* it the {@link UI.Color#TRANSPARENT} constant.<br>
* Alternatively, you may use the {@link #peek(Peeker)}
* method to peek into the builder's component
* and set the flag directly.
*/
@Deprecated
public final I makeNonOpaque() {
return _with( c -> c.setOpaque( false ) )._this();
}
/**
* This allows you to register validation logic for the wrapped UI component.
* Although the delegate exposed to the {@link UIVerifier} lambda
* indirectly exposes you to the UIs state, you should not access the UI directly
* from within the lambda, but modify the properties inside your view model instead.
*
* @param verifier The validation logic provided by your view model.
* @return This very instance, which enables builder-style method chaining.
*/
public final I isValidIf( UIVerifier<C> verifier ) {
return _with( c -> {
c.setInputVerifier(new InputVerifier() {
@Override
public boolean verify( JComponent input ) {
return verifier.isValid(
new ComponentDelegate<>(
c,
new ComponentEvent(c, 0)
)
);
/*
We expect the user to model the state of the UI components
using properties in the view model.
*/
}
});
})
._this();
}
/**
* Adds {@link String} key/value "client property" pairs to the wrapped component.
* <p>
* The arguments will be passed to {@link JComponent#putClientProperty(Object, Object)}
* which accesses
* a small per-instance hashtable. Callers can use get/putClientProperty
* to annotate components that were created by another module.
* For example, a
* layout manager might store per child constraints this way. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.button()
* .peek( button -> button.putClientProperty("key", "value") );
* }</pre>
*
* @param key the new client property key which may be used for styles or layout managers.
* @param value the new client property value.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withProperty( String key, String value ) {
return _with( c -> c.putClientProperty(key, value) )._this();
}
/**
* Use this to attach a border to the wrapped component.
*
* @param border The {@link Border} which should be set for the wrapped component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBorder( Border border ) {
Objects.requireNonNull(border, "Null value for border is not allowed! Use an empty border instead!");
return _with( c -> c.setBorder(border) )._this();
}
/**
* Use this to dynamically attach a border to the wrapped component. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param border The {@link Border} which should be set for the wrapped component wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBorder( Val<Border> border ) {
NullUtil.nullArgCheck(border, "border", Val.class);
NullUtil.nullPropertyCheck(border, "border", "Null value for border is not allowed! Use an empty border instead!");
return _withOnShow( border, (c,v) -> {
c.setBorder(v);
})
._with( c -> {
c.setBorder( border.orElseThrow() );
})
._this();
}
/**
* Use this to define an empty {@link Border} with the provided insets.
*
* @param top The top inset.
* @param left The left inset.
* @param bottom The bottom inset.
* @param right The right inset.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorder( int top, int left, int bottom, int right ) {
return _with( c -> c.setBorder(BorderFactory.createEmptyBorder(top, left, bottom, right)) )._this();
}
/**
* Use this to define a titled empty {@link Border} with the provided insets.
*
* @param title The title of the border.
* @param top The top inset.
* @param left The left inset.
* @param bottom The bottom inset.
* @param right The right inset.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( String title, int top, int left, int bottom, int right ) {
NullUtil.nullArgCheck( title, "title", String.class );
return _with( c -> c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(top, left, bottom, right),
title
)
)
)
._this();
}
/**
* Use this to define a titled empty {@link Border} with the provided insets
* and where the title is bound to a {@link Val}.
*
* @param title The title of the border wrapped in a {@link Val},
* which will update the border title dynamically when changed.
* @param top The top inset.
* @param left The left inset.
* @param bottom The bottom inset.
* @param right The right inset.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( Val<String> title, int top, int left, int bottom, int right ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck( title, "title", "Null value for title is not allowed! Use an empty string instead!" );
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(top, left, bottom, right),
v
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(top, left, bottom, right),
title.orElseThrow()
)
);
})
._this();
}
/**
* Use this to define an empty {@link Border} with the provided insets.
*
* @param topBottom The top and bottom insets.
* @param leftRight The left and right insets.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorder( int topBottom, int leftRight ) {
return withEmptyBorder( topBottom, leftRight, topBottom, leftRight );
}
/**
* Use this to define a titled empty {@link Border} with the provided insets.
*
* @param title The title of the border.
* @param topBottom The top and bottom insets.
* @param leftRight The left and right insets.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( String title, int topBottom, int leftRight ) {
NullUtil.nullArgCheck( title, "title", String.class );
return withEmptyBorderTitled( title, topBottom, leftRight, topBottom, leftRight );
}
/**
* Use this to define a titled empty {@link Border} with the provided insets
* and where the title is bound to a {@link Val}.
*
* @param title The title of the border wrapped in a {@link Val}. When the value changes, the border title will be updated.
* @param topBottom The top and bottom insets.
* @param leftRight The left and right insets.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( Val<String> title, int topBottom, int leftRight ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty string instead!");
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(topBottom, leftRight, topBottom, leftRight),
v
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(topBottom, leftRight, topBottom, leftRight),
title.orElseThrow()
)
);
})
._this();
}
/**
* Use this to define an empty {@link Border} with the provided insets.
*
* @param all The insets for all sides.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorder( int all ) { return withEmptyBorder(all, all, all, all); }
/**
* Creates an empty and un-titled {@link Border} with the provided insets
* property bound to all insets of said border.
* <p>
* An empty and un-titled {@link Border} is basically just a way to add some
* space around the component. It is not visible by default.
*
* @param all The insets for all sides.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorder( Val<Integer> all ) {
NullUtil.nullArgCheck( all, "all", Val.class );
NullUtil.nullPropertyCheck(all, "all", "Null value for all is not allowed! Use an empty border instead!");
return _withOnShow( all, (c,v) -> {
c.setBorder(BorderFactory.createEmptyBorder(v, v, v, v));
})
._with( c -> {
c.setBorder(BorderFactory.createEmptyBorder(all.orElseThrow(), all.orElseThrow(), all.orElseThrow(), all.orElseThrow()));
})
._this();
}
/**
* Use this to define a titled empty {@link Border} with the provided insets.
*
* @param title The title of the border.
* @param all The insets for all sides.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( String title, int all ) {
NullUtil.nullArgCheck( title, "title", String.class );
return withEmptyBorderTitled(title, all, all, all, all);
}
/**
* Creates a titled empty border bound to a {@link String} property and the provided insets.
* @param title The title of the border in the form of a {@link Val} property.
* @param all The insets size for all sides.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( Val<String> title, int all ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty string instead!");
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(all, all, all, all),
v
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createEmptyBorder(all, all, all, all),
title.orElseThrow()
)
);
})
._this();
}
/**
* Use this to define an empty {@link Border} with a title
* and a default insets size of 5.
*
* @param title The title of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( String title ) {
NullUtil.nullArgCheck( title, "title", String.class );
return withEmptyBorderTitled(title, 5);
}
/**
* Creates a titled empty border bound to a {@link String} property
* and a default insets size of 5.
*
* @param title The title of the border in the form of a {@link Val} property.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withEmptyBorderTitled( Val<String> title ) {
return withEmptyBorderTitled(title, 5);
}
/**
* Use this to define a line {@link Border} with the provided color and insets.
*
* @param color The color of the line border.
* @param thickness The thickness of the line border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorder( Color color, int thickness ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return _with( c -> c.setBorder(BorderFactory.createLineBorder(color, thickness)) )._this();
}
/**
* Creates a line border bound to a {@link Color} property.
* When the color changes, the border will be updated with the new color.
* @param color The color of the border in the form of a {@link Val} property.
* @param thickness The thickness of the line border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorder( Val<Color> color, int thickness ) {
NullUtil.nullArgCheck( color, "color", Val.class );
NullUtil.nullPropertyCheck(color, "color", "Null value for color is not allowed! Use a transparent color or other default color instead!");
return _withOnShow( color, (c,v) -> {
c.setBorder(BorderFactory.createLineBorder(v, thickness));
})
._with( c -> {
c.setBorder(BorderFactory.createLineBorder(color.orElseThrow(), thickness));
})
._this();
}
/**
* Use this to define a titled line {@link Border} with the provided color and insets.
*
* @param title The title of the border.
* @param color The color of the line border.
* @param thickness The thickness of the line border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorderTitled( String title, Color color, int thickness ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return _with( c -> c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color, thickness),
title
)
)
)
._this();
}
/**
* Creates a titled line border bound to a {@link String} property.
* @param title The title property of the border which will update the border when the value changes.
* @param color The color of the border.
* @param thickness The thickness of the line border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorderTitled( Val<String> title, Color color, int thickness ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty string instead!");
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color, thickness),
v
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color, thickness),
title.orElseThrow()
)
);
})
._this();
}
/**
* Creates a titled line border bound to a {@link String} property
* and a {@link Color} property.
* When any of the properties change, the border will be updated with the new values.
*
* @param title The title property of the border which will update the border when the value changes.
* @param color The color property of the border which will update the border when the value changes.
* @param thickness The thickness of the line border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorderTitled( Val<String> title, Val<Color> color, int thickness ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty string instead!");
NullUtil.nullArgCheck( color, "color", Val.class );
NullUtil.nullPropertyCheck(color, "color", "Null value for color is not allowed! Use a transparent color or other default color instead!");
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color.orElseThrow(), thickness),
v
)
);
})
._withOnShow( color, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(v, thickness),
title.orElseThrow()
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color.orElseThrow(), thickness),
title.orElseThrow()
)
);
})
._this();
}
/**
* Use this to define a line {@link Border} with the provided color and a default thickness of {@code 1}.
*
* @param color The color of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorder( Color color ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return withLineBorder(color, 1);
}
/**
* Use this to define a titled line {@link Border} with the provided color and a default thickness of {@code 1}.
*
* @param title The title of the border.
* @param color The color of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLineBorderTitled( String title, Color color ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return withLineBorderTitled( title, color, 1 );
}
/**
* Use this to attach a rounded line {@link Border} with the provided
* color and insets to the {@link JComponent}.
*
* @param color The color of the border.
* @param thickness The thickness of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorder( Color color, int thickness ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return _with( c -> c.setBorder(BorderFactory.createLineBorder(color, thickness, true)) )._this();
}
/**
* Use this to attach a titled rounded line {@link Border} with the provided
* color and insets to the {@link JComponent}.
*
* @param title The title of the border.
* @param color The color of the border.
* @param thickness The thickness of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( String title, Color color, int thickness ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return _with( c -> c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color, thickness, true),
title
)
)
)
._this();
}
/**
* Creates a titled rounded line {@link Border} with the provided
* color and insets for this {@link JComponent} and binds the border to the provided
* title property.
* When the title property changes, the border will be updated with the new value.
*
* @param title The title property of the border which will update the border when the value changes.
* @param color The color of the border.
* @param thickness The thickness of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( Val<String> title, Color color, int thickness ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty string instead!");
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color, thickness, true),
v
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color, thickness, true),
title.orElseThrow()
)
);
})
._this();
}
/**
* Creates a titled rounded line {@link Border} with the provided
* color and insets for this {@link JComponent} and binds the border to the provided
* title and color properties.
* When the title or color properties change,
* then the border will be updated with the new values.
*
* @param title The title property of the border which will update the border when the value changes.
* @param color The color property of the border which will update the border when the value changes.
* @param thickness The thickness of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( Val<String> title, Val<Color> color, int thickness ) {
NullUtil.nullArgCheck( title, "title", Val.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty string instead!");
NullUtil.nullArgCheck( color, "color", Val.class );
NullUtil.nullPropertyCheck(color, "color", "Null value for color is not allowed! Use a transparent color or other default color instead!");
return _withOnShow( title, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color.orElseThrow(), thickness, true),
v
)
);
})
._withOnShow( color, (c,v) -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(v, thickness, true),
title.orElseThrow()
)
);
})
._with( c -> {
c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(color.orElseThrow(), thickness, true),
title.orElseThrow()
)
);
})
._this();
}
/**
* Use this to attach a rounded line {@link Border} with the provided
* color and a default thickness of {@code 1} to the {@link JComponent}.
*
* @param color The color of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorder( Color color ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return withRoundedLineBorder(color, 1);
}
/**
* Use this to attach a titled rounded line {@link Border} with the provided
* color property and a custom thickness to the {@link JComponent}.
*
* @param color The color of the border.
* @param thickness The thickness of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorder( Val<Color> color, int thickness ) {
NullUtil.nullArgCheck( color, "color", Val.class );
NullUtil.nullPropertyCheck(color, "color", "Null value for color is not allowed! Use a transparent color or other default color instead!");
return _withOnShow( color, (c,v) -> {
c.setBorder(BorderFactory.createLineBorder(v, thickness, true));
})
._with( c -> {
c.setBorder(BorderFactory.createLineBorder(color.orElseThrow(), thickness, true));
})
._this();
}
/**
* Use this to attach a titled rounded line {@link Border} with the provided
* title, color and a default thickness of {@code 1} to the {@link JComponent}.
*
* @param title The title of the border.
* @param color The color of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( String title, Color color ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return withRoundedLineBorderTitled( title, color, 1 );
}
/**
* Use this to attach a titled rounded line {@link Border} with the provided
* title and color to the {@link JComponent}, as well as a default thickness of {@code 1}.
*
* @param title The title property of the border, which will update the border when the property changes.
* @param color The color property of the border, which will update the border when the property changes.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( Val<String> title, Val<Color> color ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty String instead!");
return withRoundedLineBorderTitled( title, color, 1 );
}
/**
* Use this to attach a rounded black line {@link Border} with
* a thickness of {@code 1} to the {@link JComponent}.
*
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorder() { return withRoundedLineBorder(Color.BLACK, 1); }
/**
* Use this to attach a titled rounded black line {@link Border} with
* a thickness of {@code 1} to the {@link JComponent}.
*
* @param title The title of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( String title ) {
NullUtil.nullArgCheck( title, "title", String.class );
return withRoundedLineBorderTitled( title, Color.BLACK, 1 );
}
/**
* Creates a titled rounded black line {@link Border} with
* a thickness of {@code 1} to the {@link JComponent} and binds it to the provided
* title property.
* When the property changes, the border will be updated.
*
* @param title The title property of the border, which will update the border when the property changes.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withRoundedLineBorderTitled( Val<String> title ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullPropertyCheck(title, "title", "Null value for title is not allowed! Use an empty String instead!");
return withRoundedLineBorderTitled( title, java.awt.Color.BLACK, 1 );
}
/**
* Use this to attach a {@link javax.swing.border.MatteBorder} with the provided
* color and insets to the {@link JComponent}.
*
* @param color The color of the border.
* @param top The top inset.
* @param left The left inset.
* @param bottom The bottom inset.
* @param right The right inset.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withMatteBorder( Color color, int top, int left, int bottom, int right ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return _with( c -> c.setBorder(BorderFactory.createMatteBorder(top, left, bottom, right, color)) )._this();
}
/**
* Use this to attach a titled {@link javax.swing.border.MatteBorder} with the provided
* color and insets to the {@link JComponent}.
*
* @param title The title of the border.
* @param color The color of the border.
* @param top The top inset.
* @param left The left inset.
* @param bottom The bottom inset.
* @param right The right inset.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withMatteBorderTitled( String title, Color color, int top, int left, int bottom, int right ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return _with( c -> c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createMatteBorder(top, left, bottom, right, color),
title
)
)
)
._this();
}
/**
* Use this to attach a {@link javax.swing.border.MatteBorder} with the provided
* color and insets to the {@link JComponent}.
*
* @param color The color of the border.
* @param topBottom The top and bottom insets.
* @param leftRight The left and right insets.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withMatteBorder( Color color, int topBottom, int leftRight ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return withMatteBorder(color, topBottom, leftRight, topBottom, leftRight);
}
/**
* Use this to attach a titled {@link javax.swing.border.MatteBorder} with the provided
* color and insets to the {@link JComponent}.
*
* @param title The title of the border.
* @param color The color of the border.
* @param topBottom The top and bottom insets.
* @param leftRight The left and right insets.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withMatteBorderTitled( String title, Color color, int topBottom, int leftRight ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return withMatteBorderTitled(title, color, topBottom, leftRight, topBottom, leftRight);
}
/**
* Use this to attach a {@link javax.swing.border.MatteBorder} with the provided
* color and insets to the {@link JComponent}.
*
* @param color The color of the border.
* @param all The insets for all sides.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withMatteBorder( Color color, int all ) {
NullUtil.nullArgCheck( color, "color", Color.class );
return withMatteBorder(color, all, all, all, all);
}
/**
* Use this to attach a titled {@link javax.swing.border.MatteBorder} with the provided
* color and insets to the {@link JComponent}.
*
* @param title The title of the border.
* @param color The color of the border.
* @param all The insets for all sides.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withMatteBorderTitled( String title, Color color, int all ) {
NullUtil.nullArgCheck( title, "title", String.class );
NullUtil.nullArgCheck( color, "color", Color.class );
return withMatteBorderTitled(title, color, all, all, all, all);
}
/**
* Use this to attach a {@link javax.swing.border.CompoundBorder} with the provided
* borders to the {@link JComponent}.
*
* @param first The first border.
* @param second The second border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withCompoundBorder( Border first, Border second ) {
NullUtil.nullArgCheck( first, "first", Border.class );
NullUtil.nullArgCheck( second, "second", Border.class );
return _with( c -> c.setBorder(BorderFactory.createCompoundBorder(first, second)) )._this();
}
/**
* Use this to attach a titled {@link javax.swing.border.CompoundBorder} with the
* provided borders to the {@link JComponent}.
*
* @param title The title of the border.
* @param first The first border.
* @param second The second border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withCompoundBorderTitled( String title, Border first, Border second ) {
return _with( c -> c.setBorder(
BorderFactory.createTitledBorder(
BorderFactory.createCompoundBorder(first, second),
title
)
)
)
._this();
}
/**
* Use this to attach a {@link javax.swing.border.TitledBorder} with the provided title.
*
* @param title The title of the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBorderTitled( String title ) {
NullUtil.nullArgCheck(title, "title", String.class, "Please use an empty String instead of null!");
return _with( c -> c.setBorder(BorderFactory.createTitledBorder(title)) )._this();
}
/**
* Use this to attach a {@link javax.swing.border.TitledBorder} with the
* provided title property dynamically setting the title String.
*
* @param title The title property for the border.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBorderTitled( Val<String> title ) {
NullUtil.nullArgCheck(title, "title", Val.class);
return _withOnShow( title, (c,t) -> {
Border foundBorder = c.getBorder();
if ( foundBorder instanceof TitledBorder )
((TitledBorder)foundBorder).setTitle(t);
else
c.setBorder(BorderFactory.createTitledBorder(t));
})
._with( c -> {
c.setBorder(BorderFactory.createTitledBorder(title.orElseThrow()));
})
._this();
}
/**
* Use this set the cursor type which should be displayed
* when hovering over the UI component wrapped by this builder.
* <br>
* Here an example of how to use this method:
* <pre>{@code
* UI.button("Click me!").withCursor(UI.Cursor.HAND);
* }</pre>
*
* @param type The {@link UI.Cursor} type defined by a simple enum exposed by this API.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withCursor( UI.Cursor type ) {
NullUtil.nullArgCheck( type, "type", UI.Cursor.class );
return _with( c -> c.setCursor( new java.awt.Cursor( type.type ) ) )._this();
}
/**
* Use this to dynamically set the cursor type which should be displayed
* when hovering over the UI component wrapped by this builder. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param type The {@link UI.Cursor} type defined by a simple enum exposed by this API wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withCursor( Val<UI.Cursor> type ) {
NullUtil.nullArgCheck( type, "type", Val.class );
NullUtil.nullPropertyCheck(type, "type", "Null is not allowed to model a cursor type.");
return _withOnShow( type, (c,t) -> {
c.setCursor( new java.awt.Cursor( t.type ) );
})
._with( c -> {
c.setCursor( new java.awt.Cursor( type.orElseThrow().type ) );
})
._this();
}
/**
* Use this to set the cursor type which should be displayed
* when hovering over the UI component wrapped by this builder
* based on boolean property determining if the provided cursor should be set ot not. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param condition The boolean property determining if the provided cursor should be set ot not.
* @param type The {@link UI.Cursor} type defined by a simple enum exposed by this API wrapped in a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withCursorIf( Val<Boolean> condition, UI.Cursor type ) {
NullUtil.nullArgCheck( condition, "condition", Val.class );
NullUtil.nullArgCheck( type, "type", UI.Cursor.class );
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the cursor selection state.");
return _withOnShow( condition, (c,v) -> {
c.setCursor( new java.awt.Cursor( v ? type.type : UI.Cursor.DEFAULT.type ) );
})
._with( c -> {
c.setCursor( new java.awt.Cursor( condition.orElseThrow() ? type.type : UI.Cursor.DEFAULT.type ) );
})
._this();
}
/**
* Use this to dynamically set the cursor type which should be displayed
* when hovering over the UI component wrapped by this builder
* based on boolean property determining if the provided cursor should be set ot not. <br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param condition The boolean property determining if the provided cursor should be set ot not.
* @param type The {@link UI.Cursor} type property defined by a simple enum exposed by this API.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withCursorIf( Val<Boolean> condition, Val<UI.Cursor> type ) {
NullUtil.nullArgCheck( condition, "condition", Val.class );
NullUtil.nullArgCheck( type, "type", Val.class );
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the cursor selection state.");
NullUtil.nullPropertyCheck(type, "type", "Null is not allowed to model a cursor type.");
return _with( thisComponent -> {
Cursor[] baseCursor = new Cursor[1];
_onShow( condition, thisComponent, (c,v) -> type.fireChange(From.VIEW_MODEL) );
_onShow( type, thisComponent, (c,v) -> {
if ( baseCursor[0] == null ) baseCursor[0] = c.getCursor();
c.setCursor( new java.awt.Cursor( condition.orElseThrow() ? v.type : baseCursor[0].getType() ) );
});
})
._with( c -> {
c.setCursor( new java.awt.Cursor( condition.orElseThrow() ? type.orElseThrow().type : UI.Cursor.DEFAULT.type ) );
})
._this();
}
/**
* Use this to set the {@link LayoutManager} of the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new FavouriteLayoutManager()) );
* }</pre>
*
* @param layout The {@link LayoutManager} which should be supplied to the wrapped component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLayout( LayoutManager layout ) {
return _with( c -> c.setLayout(layout) )._this();
}
/**
* Use this to set a {@link FlowLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new FlowLayout()) );
* }</pre>
*
* @return This very instance, which enables builder-style method chaining.
*/
public final I withFlowLayout() { return this.withLayout(new FlowLayout()); }
/**
* Use this to set a {@link FlowLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new FlowLayout(alignment.forFlowLayout())) );
* }</pre>
*
* @param alignment The alignment of the layout.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withFlowLayout( UI.HorizontalAlignment alignment ) {
NullUtil.nullArgCheck( alignment, "alignment", UI.HorizontalAlignment.class );
return this.withLayout(
alignment.forFlowLayout().map( FlowLayout::new ).orElse(new FlowLayout())
);
}
/**
* Use this to set a {@link FlowLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new FlowLayout(alignment.forFlowLayout(), hgap, vgap)) );
* }</pre>
*
* @param alignment The alignment of the layout.
* @param hgap The horizontal gap between components.
* @param vgap The vertical gap between components.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withFlowLayout( UI.HorizontalAlignment alignment, int hgap, int vgap ) {
NullUtil.nullArgCheck( alignment, "alignment", UI.HorizontalAlignment.class );
return this.withLayout(
alignment.forFlowLayout()
.map( a -> new FlowLayout(a, hgap, vgap) )
.orElse(new FlowLayout(FlowLayout.CENTER, hgap, vgap))
);
}
/**
* Use this to set a {@link GridLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new GridLayout()) );
* }</pre>
*
* @return This very instance, which enables builder-style method chaining.
*/
public final I withGridLayout() { return this.withLayout(new GridLayout()); }
/**
* Use this to set a new {@link GridBagLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new GridBagLayout()) );
* }</pre>
* ...or specifying the layout manager like so: <br>
* <pre>{@code
* UI.panel().withLayout( new GridBagLayout() );
* }</pre>
*
* @return This very instance, which enables builder-style method chaining.
*/
public final I withGridBagLayout() { return this.withLayout(new GridBagLayout()); }
/**
* Use this to set a {@link GridLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new GridLayout(rows, cols)) );
* }</pre>
*
* @param rows The number of rows in the grid.
* @param cols The number of columns in the grid.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withGridLayout( int rows, int cols ) { return this.withLayout(new GridLayout(rows, cols)); }
/**
* Use this to set a {@link GridLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new GridLayout(rows, cols, hgap, vgap)) );
* }</pre>
*
* @param rows The number of rows in the grid.
* @param cols The number of columns in the grid.
* @param hgap The horizontal gap between cells.
* @param vgap The vertical gap between cells.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withGridLayout( int rows, int cols, int hgap, int vgap ) {
return this.withLayout(new GridLayout(rows, cols, hgap, vgap));
}
/**
* Use this to set a {@link BoxLayout} for the component wrapped by this builder. <br>
* This is in essence a more convenient way than the alternative usage pattern involving
* the {@link #peek(Peeker)} method to peek into the builder's component like so: <br>
* <pre>{@code
* UI.panel()
* .peek( panel -> panel.setLayout(new BoxLayout(panel, axis.forBoxLayout())) );
* }</pre>
*
* @param axis The axis for the box layout.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If the provided axis is {@code null}.
* @see UI.Axis
* @see BoxLayout
*/
public final I withBoxLayout( UI.Axis axis ) {
NullUtil.nullArgCheck( axis, "axis", UI.Axis.class );
return _with( c -> c.setLayout(new BoxLayout(c, axis.forBoxLayout())) )._this();
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder,
* based on the provided layout-constraints in the form of a simple string
* which is parsed by the {@link ConstraintParser} class into {@link LC} and {@link AC} instances.
* (also see {@link #withLayout(LC, AC, AC)}) <br> <br>
* A typical usage pattern would be like so: <br>
* <pre>{@code
* UI.of(new MyCustomPanel())
* .withLayout("fill wrap 2");
* .add( UI.button("Name:") )
* .add( UI.textArea() )
* .add(...)
* ...
* }</pre>
* In this example a new {@link MigLayout} is created which
* will wrap the components in the layout grid after 2 columns
* and fill the entire available space of the parent container.
* <br>
* Note that if not explicitly specified, the default {@code hidemode} will be set to 2, which means that
* when a component is hidden, it will not take up any space and the gaps around it will
* be collapsed. <br>
* Here an overview of the available hidemode values:
* <ul>
* <li><b>0:</b><br>
* Invisible components will be handled exactly as if they were visible.
* </li>
* <li><b>1:</b><br>
* The size of the component (if invisible) will be set to 0, 0.
* </li>
* <li><b>2 (SwingTree default):</b><br>
* The size of the component (if invisible) will be set to 0, 0 and the gaps
* will also be set to 0 around it.
* </li>
* <li><b>3:</b><br>
* Invisible components will not participate in the layout at all and it will
* for instance not take up a grid cell.
* </li>
* </ul>
*
* @param attr The constraints concerning the entire layout.
* Passing {@code null} will result in an exception, use an empty string instead.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( String attr ) {
NullUtil.nullArgCheck( attr, "attr", String.class );
return withLayout(attr, "");
}
/**
* Creates a new {@link MigLayout} for the component wrapped by this UI builder,
* based on the provided layout constraints in the form of a {@link LC} instance,
* which is a builder for the layout constraints.
*
* @param attr A string defining the constraints concerning the entire layout.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( LC attr ) {
NullUtil.nullArgCheck( attr, "attr", LC.class );
return withLayout(attr, (AC) null, (AC) null);
}
/**
* Creates a new {@link MigLayout} for the component wrapped by this UI builder,
* based on the provided layout constraints in the form of a {@link LayoutConstraint} instance,
* which is an immutable string wrapper for the layout constraints.
* Instances of this are usually obtained from the {@link UI} namespace like
* {@link UI#FILL} or {@link UI#FILL_X}...
*
* @param attr Essentially an immutable string wrapper defining the mig layout.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
*/
public final I withLayout( LayoutConstraint attr ) {
NullUtil.nullArgCheck( attr, "attr", LayoutConstraint.class );
return withLayout(attr.toString(), "");
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder
* based on the provided layout constraints in the form of a string.
*
* @param attr A string defining constraints for the entire layout.
* @param colConstrains The layout constraints for the columns int the {@link MigLayout} instance.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( String attr, String colConstrains ) {
NullUtil.nullArgCheck(attr, "attr", String.class, "Please use an empty String instead of null!");
NullUtil.nullArgCheck(colConstrains, "colConstrains", String.class, "Please use an empty String instead of null!");
return withLayout(attr, colConstrains, "");
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder
* based on the provided layout constraints in the form of a {@link LC} instance
* and column constraints in the form of a {@link AC} instance.
*
* @param attr The constraints for the layout, a {@link LC} instance.
* @param colConstrains The column layout for the {@link MigLayout} instance as a {@link AC} instance.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( LC attr, AC colConstrains ) {
return withLayout(attr, colConstrains, null);
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder
* based on the provided layout constraints in the form of a {@link LC} instance
* and column constraints in the form of a simple string.
*
* @param attr The constraints for the layout, a {@link LC} instance.
* @param colConstrains The column layout for the {@link MigLayout} instance as a simple string.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( LC attr, String colConstrains ) {
AC parsedColConstrains = colConstrains == null ? null : ConstraintParser.parseColumnConstraints(colConstrains);
return withLayout(attr, parsedColConstrains, null);
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder
* based on the provided layout constraints in the form of a {@link LC} instance
* and column and row constraints in the form of a simple string.
*
* @param attr The constraints for the layout, a {@link LC} instance.
* @param colConstrains The column layout for the {@link MigLayout} instance as a simple string.
* @param rowConstraints The row layout for the {@link MigLayout} instance as a simple string.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( LC attr, String colConstrains, String rowConstraints ) {
NullUtil.nullArgCheck(attr, "attr", LC.class);
NullUtil.nullArgCheck(colConstrains, "colConstrains", String.class, "Please use an empty String instead of null!");
NullUtil.nullArgCheck(rowConstraints, "rowConstraints", String.class, "Please use an empty String instead of null!");
AC parsedColConstrains = colConstrains.isEmpty() ? null : ConstraintParser.parseColumnConstraints(colConstrains);
AC parsedRowConstrains = rowConstraints.isEmpty() ? null : ConstraintParser.parseRowConstraints(rowConstraints);
return withLayout(attr, parsedColConstrains, parsedRowConstrains);
}
/**
* Takes the supplied layout constraints and column constraints
* uses them to construct a new {@link MigLayout} for the component wrapped by this UI builder.
*
* @param attr The constraints for the layout.
* @param colConstrains The column layout for the {@link MigLayout} instance.
* @return This very instance, which enables builder-style method chaining.
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( LayoutConstraint attr, String colConstrains ) {
NullUtil.nullArgCheck(attr, "attr", LayoutConstraint.class);
NullUtil.nullArgCheck(colConstrains, "colConstrains", String.class, "Please use an empty String instead of null!");
return withLayout(attr.toString(), colConstrains, "");
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder.
*
* @param attr The constraints for the layout in the form of a {@link LayoutConstraint} instance.
* @param colConstrains The column layout for the {@link MigLayout} instance.
* @param rowConstraints The row layout for the {@link MigLayout} instance.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLayout( LayoutConstraint attr, String colConstrains, String rowConstraints ) {
NullUtil.nullArgCheck(attr, "attr", LayoutConstraint.class);
NullUtil.nullArgCheck(colConstrains, "colConstrains", String.class, "Please use an empty String instead of null!");
return withLayout(attr.toString(), colConstrains, rowConstraints);
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder,
* based on the provided layout-, column- and row-constraints in the form of simple strings,
* which are parsed by the {@link ConstraintParser} class into {@link LC} and {@link AC} instances.
* (also see {@link #withLayout(LC, AC, AC)}) <br> <br>
* A typical usage pattern would be like so: <br>
* <pre>{@code
* UI.of(new MyCustomPanel())
* .withLayout("wrap 2", "[]6[]", "[]8[]");
* .add( UI.label("Name:") )
* .add( UI.textField() )
* .add(...)
* ...
* }</pre>
* In this example a new {@link MigLayout} is created which
* will wrap the components in the layout grid after 2 columns,
* where the 2 columns are separated by a 6 pixel gap and the rows
* are separated by an 8 pixel gap. <br>
* <br>
* Note that if not explicitly specified, the default {@code hidemode} will be set to 2, which means that
* when a component is hidden, it will not take up any space and the gaps around it will
* be collapsed. <br>
* Here an overview of the available hidemode values:
* <ul>
* <li><b>0:</b><br>
* Invisible components will be handled exactly as if they were visible.
* </li>
* <li><b>1:</b><br>
* The size of the component (if invisible) will be set to 0, 0.
* </li>
* <li><b>2 (SwingTree default):</b><br>
* The size of the component (if invisible) will be set to 0, 0 and the gaps
* will also be set to 0 around it.
* </li>
* <li><b>3:</b><br>
* Invisible components will not participate in the layout at all and it will
* for instance not take up a grid cell.
* </li>
* </ul>
*
* @param constraints The constraints concerning the entire layout.
* Passing {@code null} will result in an exception, use an empty string instead.
* @param colConstrains The column layout for the {@link MigLayout} instance,
* which concern the columns in the layout grid.
* Passing {@code null} will result in an exception, use an empty string instead.
* @param rowConstraints The row layout for the {@link MigLayout} instance,
* which concern the rows in the layout grid.
* Passing {@code null} will result in an exception, use an empty string instead.
* @return This very instance, which enables builder-style method chaining.
*
* @throws IllegalArgumentException If any of the arguments are {@code null}.
* @see <a href="http://www.miglayout.com/QuickStart.pdf">Quick Start Guide</a>
*/
public final I withLayout( String constraints, String colConstrains, String rowConstraints ) {
NullUtil.nullArgCheck(constraints, "constraints", String.class, "Please use an empty String instead of null!");
NullUtil.nullArgCheck(colConstrains, "colConstrains", String.class, "Please use an empty String instead of null!");
NullUtil.nullArgCheck(rowConstraints, "rowConstraints", String.class, "Please use an empty String instead of null!");
// We make sure the default hidemode is 2 instead of 3 (which sucks because it takes up too much space)
if ( constraints.isEmpty() )
constraints = "hidemode 2";
else if ( !constraints.contains("hidemode") )
constraints += ", hidemode 2";
constraints = ( constraints.isEmpty() ? null : constraints );
colConstrains = ( colConstrains.isEmpty() ? null : colConstrains );
rowConstraints = ( rowConstraints.isEmpty() ? null : rowConstraints );
MigLayout migLayout = new MigLayout(constraints, colConstrains, rowConstraints);
return _with( c -> c.setLayout(migLayout) )._this();
}
/**
* This creates a {@link MigLayout} for the component wrapped by this UI builder.
*
* @param attr The constraints for the layout.
* @param colConstrains The column layout for the {@link MigLayout} instance.
* @param rowConstraints The row layout for the {@link MigLayout} instance.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withLayout( @Nullable LC attr, @Nullable AC colConstrains, @Nullable AC rowConstraints ) {
// We make sure the default hidemode is 2 instead of 3 (which sucks because it takes up too much space)
if ( attr == null )
attr = new LC().hideMode(2);
else if ( attr.getHideMode() == 0 )
attr = attr.hideMode(2);
MigLayout migLayout = new MigLayout(attr, colConstrains, rowConstraints);
return _with( c -> c.setLayout(migLayout) )._this();
}
/**
* Use this to set a helpful tool tip text for this UI component.
* The tool tip text will be displayed when the mouse hovers on the
* UI component for some time. <br>
* This is in essence a convenience method, which avoid having to expose the underlying component
* through the {@link #peek(Peeker)} method like so: <br>
* <pre>{@code
* UI.button("Click Me")
* .peek( button -> button.setToolTipText("Can be clicked!") );
* }</pre>
*
* @param tooltip The tool tip text which should be set for the UI component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withTooltip( String tooltip ) {
NullUtil.nullArgCheck(tooltip, "tooltip", String.class, "Use the empty string to clear the tooltip text!");
return _with( c -> c.setToolTipText(tooltip.isEmpty() ? null : tooltip) )._this();
}
/**
* Use this to bind to a {@link sprouts.Val}
* containing a tooltip string.
* This is a convenience method, which would
* be equivalent to:
* <pre>{@code
* UI.button("Click Me")
* .peek( button -> {
* tip.onSetItem(JButton::setToolTipText);
* button.setToolTipText(tip.get());
* });
* }</pre><br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param tip The tooltip which should be displayed when hovering over the tab header.
* @return A new {@link Tab} instance with the provided argument, which enables builder-style method chaining.
*/
public final I withTooltip( Val<String> tip ) {
NullUtil.nullArgCheck(tip, "tip", Val.class);
NullUtil.nullPropertyCheck(tip, "tip", "Please use an empty string instead of null!");
return _withOnShow( tip, (c,v) -> {
c.setToolTipText( v.isEmpty() ? null : v );
})
._with( c -> {
String tipString = tip.orElse("");
c.setToolTipText( tipString.isEmpty() ? null : tipString );
})
._this();
}
/**
* Use this to set the background color of the UI component
* wrapped by this builder.<br>
* This is in essence a convenience method, which avoid having to expose the underlying component
* through the {@link #peek(Peeker)} method like so: <br>
* <pre>{@code
* UI.label("Something")
* .peek( label -> label.setBackground(Color.CYAN) );
* }</pre>
*
* @param color The background color which should be set for the UI component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBackground( Color color ) {
NullUtil.nullArgCheck(color, "color", Color.class);
return _with( c -> _setBackground(c, color) )
._this();
}
@SuppressWarnings("ReferenceEquality")
protected void _setBackground( JComponent thisComponent, Color color ) {
color = _isUndefinedColor(color) ? null : color;
thisComponent.setBackground( color );
color = thisComponent.getBackground();
// ^ If the provided color is null the component may inherit the color from its parent!
if ( color == UI.Color.TRANSPARENT ) {
thisComponent.setOpaque(false);
}
}
/**
* Use this to bind to a {@link sprouts.Val}
* containing a background color.
* This is a convenience method, which would
* be equivalent to:
* <pre>{@code
* UI.button("Click Me")
* .peek( button -> {
* bg.onSetItem(JButton::setBackground);
* button.setBackground(bg.get());
* });
* }</pre><br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param bg The background color which should be set for the UI component wrapped by a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBackground( Val<Color> bg ) {
NullUtil.nullArgCheck(bg, "bg", Val.class);
NullUtil.nullPropertyCheck(bg, "bg", "Please use the default color of this component instead of null!");
return _withOnShow( bg, (c,v) -> {
c.setBackground( _isUndefinedColor(v) ? null : v );
})
._with( c -> {
c.setBackground( _isUndefinedColor(bg.get()) ? null : bg.get() );
})
._this();
}
/**
* Use this to bind to a background color
* which will be set dynamically based on a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param colorIfTrue The background color which should be set for the UI component.
* @param condition The condition property which determines whether the background color should be set or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBackgroundIf( Val<Boolean> condition, Color colorIfTrue ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(colorIfTrue, "bg", Color.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided background color!");
return _with( thisComponent -> {
Var<Color> baseColor = Var.of( thisComponent.getBackground() );
Var<Color> color = Var.of( colorIfTrue );
_onShow( condition, thisComponent, (c,v) -> _updateBackground( c, condition, color, baseColor ) );
})
._with( c -> {
Color newColor = condition.get() ? colorIfTrue : c.getBackground();
c.setBackground( _isUndefinedColor(newColor) ? null : newColor );
})
._this();
}
/**
* Use this to dynamically bind to a background color
* which will be set dynamically based on a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param color The background color property which should be set for the UI component.
* @param condition The condition property which determines whether the background color should be set or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBackgroundIf( Val<Boolean> condition, Val<Color> color ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(color, "color", Val.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided background color!");
NullUtil.nullPropertyCheck(color, "color", "Null is not allowed to model the the provided background color! Please use the default color of this component instead.");
return _with( thisComponent -> {
Var<Color> baseColor = Var.of( thisComponent.getBackground() );
_onShow( condition, thisComponent, (c,v) -> _updateBackground( c, condition, color, baseColor ) );
_onShow( color, thisComponent, (c,v) -> _updateBackground( c, condition, color, baseColor ) );
})
._with( c -> {
Color newColor = condition.get() ? color.get() : c.getBackground();
c.setBackground( _isUndefinedColor(newColor) ? null : newColor );
})
._this();
}
/**
* Use this to bind to 2 colors to the background of the component
* which sre set based on the value of a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param condition The condition property which determines whether the background color should be set or not.
* @param colorIfTrue The background color which should be set for the UI component.
* @param colorIfFalse The background color which should be set for the UI component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBackgroundIf( Val<Boolean> condition, Color colorIfTrue, Color colorIfFalse ) {
return this.withBackgroundIf( condition, Var.of(colorIfTrue), Var.of(colorIfFalse) );
}
/**
* Use this to bind to 2 color properties to the background of the component
* which sre set based on the value of a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param condition The condition property which determines whether the background color should be set or not.
* @param colorIfTrue The background color which should be set for the UI component.
* @param colorIfFalse The background color which should be set for the UI component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withBackgroundIf( Val<Boolean> condition, Val<Color> colorIfTrue, Val<Color> colorIfFalse ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(colorIfTrue, "colorIfTrue", Val.class);
NullUtil.nullArgCheck(colorIfFalse, "colorIfFalse", Val.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided background color!");
return _withOnShow( condition, (c,v) -> {
_updateBackground( c, condition, colorIfTrue, Var.of(colorIfFalse.get()) );
})
._withOnShow( colorIfTrue, (c,v) -> {
_updateBackground( c, condition, colorIfTrue, Var.of(colorIfFalse.get()) );
})
._withOnShow( colorIfFalse, (c,v) -> {
_updateBackground( c, condition, colorIfTrue, Var.of(colorIfFalse.get()) );
})
._with( c -> {
Color newColor = condition.get() ? colorIfTrue.get() : colorIfFalse.get();
c.setBackground( _isUndefinedColor(newColor) ? null : newColor );
})
._this();
}
/**
* Allows you to configure how the component wrapped by this builder
* looks and behaves, by passing a {@link Styler} lambda to this method
* which receiving a {@link swingtree.style.ComponentStyleDelegate} and returns
* an updated version with the desired style rules applied.
* <p>
* Here a typical example of how to style a button
* using the style API:
* <pre>{@code
* UI.button("Click Me!")
* .withStyle( it -> it
* .borderColor(Color.CYAN)
* .borderWidthAt(Edge.BOTTOM, 3)
* .borderRadius(10)
* )
* }</pre>
* <p>
* Here the {@code it} variable is the {@link swingtree.style.ComponentStyleDelegate} which
* exposes an extensive API for configuring how a particular component
* looks and behaves.
* <p>
* If you want to define style rules for an entire GUI or a part of it,
* take a look at the {@link swingtree.style.StyleSheet} class,
* which exposes an API for defining style rules similar to CSS
* but based on declarative source code instead of a text file.
*
* @param styler A {@link Styler} lambda can define a set of style rules for the component wrapped by this builder
* by receiving a {@link swingtree.style.ComponentStyleDelegate} and returning
* an updated version with the desired style rules applied.
*
* @return This very instance, which enables builder-style method chaining.
*/
public final I withStyle( Styler<C> styler ) {
NullUtil.nullArgCheck(styler, "styler", Styler.class);
return _with( c -> {
ComponentExtension.from(c).addStyler( styler );
})
._this();
}
/**
* Here an example demonstrating how a transitional style can be applied
* to make a border which can transition between 2 colors based on a boolean property:
* <pre>{@code
* UI.button("Click Me!")
* .withTransitionalStyle(vm.isError(), LifeTime.of(1, TimeUnit.SECONDS), (state, it) -> it
* .backgroundColor(Color.CYAN)
* .border(3, new Color((int)(state.progress() * 255), 0, 0))
* )
* }</pre>
*
*
* @param transitionToggle The boolean {@link Val} property which determines the state to which the style should transition.
* When the value of this property is {@code true}, the style will transition to a {@link AnimationState#progress()}
* of {@code 1.0} over the provided {@link LifeTime}.
* And when the value of this property is {@code false}, the style will transition to a {@link AnimationState#progress()}
* of {@code 0.0} over the provided {@link LifeTime}.
*
* @param transitionLifeTime The {@link LifeTime} of the transition animation.
* It defines for ow long the {@link AnimationState#progress()} will transition from {@code 0} to {@code 1} or vice versa.
*
* @param styler An {@link AnimatedStyler} lambda can define a set of style rules for the component wrapped by this builder
* by receiving an {@link AnimationState} and a {@link swingtree.style.ComponentStyleDelegate} and returning
* an updated version with the desired style rules applied.
* The {@link AnimatedStyler} may apply the style properties according to the {@link AnimationState}
* and its {@link AnimationState#progress()} method (or other methods) to create a smooth
* transition between the 2 states.
*
* @return This builder instance, which enables fluent method chaining.
* @see #withTransitoryStyle(Event, LifeTime, AnimatedStyler)
*/
public final I withTransitionalStyle(
Val<Boolean> transitionToggle,
LifeTime transitionLifeTime,
AnimatedStyler<C> styler
) {
NullUtil.nullArgCheck(transitionToggle, "transitionToggle", Val.class);
NullUtil.nullArgCheck(transitionLifeTime, "transitionLifeTime", LifeTime.class);
NullUtil.nullArgCheck(styler, "styler", AnimatedStyler.class);
return _with( c -> {
FlipFlopStyler<C> flipFlopStyler = new FlipFlopStyler<>(c, transitionLifeTime, styler);
ComponentExtension.from(c).addStyler(flipFlopStyler::style);
_onShow( transitionToggle, c, (comp, v) -> flipFlopStyler.set(v) );
})
._this();
}
/**
* Allows you to configure a style which will be applied to the component temporarily
* when the provided {@link Event} is fired. The style will be applied for the provided
* {@link LifeTime} and then removed again.
* Here an example demonstrating how an event based style animation which temporarily
* defines a custom background and border color on a label:
* <pre>{@code
* UI.label("I have a highlight animation!")
* .withTransitoryStyle(vm.highlightEvent(), LifeTime.of(0.5, TimeUnit.SECONDS), (state, it) -> it
* .backgroundColor(new Color(0, 0, 0, (int)(state.progress() * 255)))
* .borderColor(new Color(255, 255, 255, (int)(state.progress() * 255)))
* )
* }</pre>
*
* @param styleEvent The {@link Event} which should trigger the style animation.
* @param styleLifeTime The {@link LifeTime} of the style animation.
* @param styler An {@link AnimatedStyler} lambda can define a set of style rules for the component wrapped by this builder
* by receiving an {@link AnimationState} and a {@link swingtree.style.ComponentStyleDelegate} and returning
* an updated version with the desired style rules applied.
* The {@link AnimatedStyler} may apply the style properties according to the {@link AnimationState}
* and its {@link AnimationState#progress()} method (or other methods) to create a smooth
* transition between the 2 states.
*
* @return This builder instance, which enables fluent method chaining.
* @see #withTransitionalStyle(Val, LifeTime, AnimatedStyler)
*/
public final I withTransitoryStyle(
Event styleEvent,
LifeTime styleLifeTime,
AnimatedStyler<C> styler
){
NullUtil.nullArgCheck(styleEvent, "styleEvent", Event.class);
NullUtil.nullArgCheck(styleLifeTime, "styleLifeTime", LifeTime.class);
NullUtil.nullArgCheck(styler, "styler", AnimatedStyler.class);
return _with( thisComponent -> {
styleEvent.subscribe( ()->{
Animator.animateFor(styleLifeTime, thisComponent).go( state ->
ComponentExtension.from(thisComponent)
.addAnimatedStyler(state, conf -> styler.style(state, conf))
);
});
})
._this();
}
/**
* Set the color of this {@link JComponent}. (This is usually the font color for components displaying text) <br>
* This is in essence a convenience method, which avoid having to expose the underlying component
* through the {@link #peek(Peeker)} method like so: <br>
* <pre>{@code
* UI.label("Something")
* .peek( label -> label.setForeground(Color.GRAY) );
* }</pre>
*
* @param color The color of the foreground (usually text).
* @return This very builder to allow for method chaining.
*/
public final I withForeground( Color color ) {
NullUtil.nullArgCheck(color, "color", Color.class);
return _with( c -> c.setForeground( _isUndefinedColor(color) ? null : color ) )._this();
}
/**
* Use this to bind to a {@link sprouts.Val}
* containing a foreground color.
* This is a convenience method, which works
* similar to:
* <pre>{@code
* UI.button("Click Me")
* .peek( button -> {
* fg.onChange(From.VIEW_MODEL, v -> button.setForeground(v.get()) );
* button.setForeground(fg.get());
* });
* }</pre><br>
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param fg The foreground color which should be set for the UI component wrapped by a {@link Val}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withForeground( Val<Color> fg ) {
NullUtil.nullArgCheck(fg, "fg", Val.class);
NullUtil.nullPropertyCheck(fg, "fg", "Please use the default color of this component instead of null!");
return _withOnShow( fg, (c,v) -> {
c.setForeground( _isUndefinedColor(v) ? null : v );
})
._with( c -> {
Color newColor = fg.get();
if ( _isUndefinedColor(newColor))
newColor = null;
c.setForeground( newColor );
})
._this();
}
/**
* Use this to bind to a foreground color
* which will be set dynamically based on a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param fg The foreground color which should be set for the UI component.
* @param condition The condition property which determines whether the foreground color should be set or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withForegroundIf( Val<Boolean> condition, Color fg ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(fg, "fg", Color.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided foreground color!");
return _with( thisComponent -> {
Var<Color> baseColor = Var.of( thisComponent.getForeground() );
Var<Color> newColor = Var.of( fg );
_onShow( condition, thisComponent, (c,v) -> _updateForeground( c, condition, newColor, baseColor ) );
})
._with( c -> {
Color newColor = condition.get() ? fg : c.getForeground();
if ( _isUndefinedColor(newColor))
newColor = null;
c.setForeground( newColor );
})
._this();
}
/**
* Use this to dynamically bind to a foreground color
* which will be set dynamically based on a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param color The foreground color property which should be set for the UI component.
* @param condition The condition property which determines whether the foreground color should be set or not.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withForegroundIf( Val<Boolean> condition, Val<Color> color ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(color, "color", Val.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided foreground color!");
NullUtil.nullPropertyCheck(color, "color", "Null is not allowed to model the the provided foreground color! Please use the default color of this component instead.");
return _with( thisComponent -> {
Var<Color> baseColor = Var.of( thisComponent.getForeground() );
_onShow( condition, thisComponent, (c,v) -> _updateForeground( c, condition, color, baseColor ) );
_onShow( color, thisComponent, (c,v) -> _updateForeground( c, condition, color, baseColor ) );
})
._with( c -> {
Color newColor = condition.get() ? color.get() : c.getForeground();
if ( _isUndefinedColor(newColor))
newColor = null;
c.setForeground( newColor );
})
._this();
}
/**
* Use this to dynamically bind to a foreground color
* which will be set dynamically based on a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param condition The condition property which determines whether the foreground color should be set or not.
* @param colorIfTrue The foreground color which should be set for the UI component.
* @param colorIfFalse The foreground color which should be set for the UI component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withForegroundIf( Val<Boolean> condition, Color colorIfTrue, Color colorIfFalse ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(colorIfTrue, "colorIfTrue", Color.class);
NullUtil.nullArgCheck(colorIfFalse, "colorIfFalse", Color.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided foreground color!");
return _withOnShow( condition, (c,v) -> {
_updateForeground( c, condition, Var.of(colorIfTrue), Var.of(colorIfFalse) );
})
._with( c -> {
Color newColor = condition.get() ? colorIfTrue : colorIfFalse;
if ( _isUndefinedColor(newColor) )
newColor = null;
c.setForeground( newColor );
})
._this();
}
/**
* Use this to dynamically bind to a foreground color
* which will be set dynamically based on a boolean property.
* <i>Hint: Use {@code myProperty.fire(From.VIEW_MODEL)} in your view model to send the property value to this view component.</i>
*
* @param condition The condition property which determines whether the foreground color should be set or not.
* @param colorIfTrue The foreground color property which should be set for the UI component.
* @param colorIfFalse The foreground color property which should be set for the UI component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I withForegroundIf( Val<Boolean> condition, Val<Color> colorIfTrue, Val<Color> colorIfFalse ) {
NullUtil.nullArgCheck(condition, "condition", Val.class);
NullUtil.nullArgCheck(colorIfTrue, "colorIfTrue", Val.class);
NullUtil.nullArgCheck(colorIfFalse, "colorIfFalse", Val.class);
NullUtil.nullPropertyCheck(condition, "condition", "Null is not allowed to model the usage of the provided foreground color!");
NullUtil.nullPropertyCheck(colorIfTrue, "colorIfTrue", "Null is not allowed to model the the provided foreground color! Please use the default color of this component instead.");
NullUtil.nullPropertyCheck(colorIfFalse, "colorIfFalse", "Null is not allowed to model the the provided foreground color! Please use the default color of this component instead.");
return _withOnShow( condition, (c,v) -> {
_updateForeground( c, condition, colorIfTrue, Var.of(colorIfFalse.get()) );
})
._withOnShow( colorIfTrue, (c,v) -> {
_updateForeground( c, condition, colorIfTrue, Var.of(colorIfFalse.get()) );
})
._withOnShow( colorIfFalse, (c,v) -> {
_updateForeground( c, condition, colorIfTrue, Var.of(colorIfFalse.get()) );
})
._with( c -> {
Color newColor = condition.get() ? colorIfTrue.get() : colorIfFalse.get();
if ( _isUndefinedColor(newColor))
newColor = null;
c.setForeground( newColor );
})
._this();
}
private void _updateForeground(
C component,
Val<Boolean> condition,
Val<Color> color,
Val<Color> baseColor
) {
Color newColor = condition.is(true) ? color.get() : baseColor.get();
if ( _isUndefinedColor(newColor))
newColor = null;
component.setForeground(newColor);
}
private void _updateBackground(
C component,
Val<Boolean> condition,
Val<Color> color,
Val<Color> baseColor
) {
Color newColor = condition.is(true) ? color.get() : baseColor.get();
if ( _isUndefinedColor(newColor) )
newColor = null;
component.setBackground(newColor);
}
/**
* Set the minimum {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component. <br>
* @param size The minimum {@link Dimension} of the component.
* @return This very builder to allow for method chaining.
*/
public final I withMinSize( Dimension size ) {
NullUtil.nullArgCheck(size, "size", Dimension.class);
return _with( c -> c.setMinimumSize(UI.scale(size)) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the maximum {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} (Dimension)} on the underlying component. <br>
* This is a convenience method, which would
* be equivalent to:
* <pre>{@code
* UI.button("Click Me")
* .peek( button -> {
* size.onSetItem(JButton::setMinimumSize);
* button.setMinimumSize(size.get());
* });
* }</pre>
*
* @param size The minimum {@link Dimension} of the component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withMinSize( Val<Dimension> size ) {
NullUtil.nullArgCheck(size, "size", Val.class);
NullUtil.nullPropertyCheck(size, "size", "Null is not allowed to model the minimum size of this component!");
return _withOnShow( size, (c,v) -> {
c.setMinimumSize(UI.scale(v));
_revalidate(c);
})
._with( c -> {
c.setMinimumSize( UI.scale(size.get()) );
})
._this();
}
/**
* Set the minimum width and heigh ({@link Dimension}) of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component. <br>
* @param width The minimum width of the component.
* @param height The minimum height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withMinSize( int width, int height ) {
return _with( c -> c.setMinimumSize(new Dimension(UI.scale(width), UI.scale(height))) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the minimum {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component. <br>
* @param width The minimum width of the component.
* @param height The minimum height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withMinSize( Val<Integer> width, Val<Integer> height ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the minimum width of this component!");
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the minimum height of this component!");
return _withOnShow( width, (c,w) -> {
c.setMinimumSize(new Dimension(UI.scale(w), c.getMinimumSize().height));
_revalidate(c);
})
._withOnShow( height, (c,h) -> {
c.setMinimumSize(new Dimension(c.getMinimumSize().width, UI.scale(h)));
_revalidate(c);
})
._with( c -> {
c.setMinimumSize( new Dimension(UI.scale(width.get()), UI.scale(height.get())) );
})
._this();
}
/**
* Use this to only set the minimum width of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component for you. <br>
* @param width The minimum width which should be set for the underlying component.
* @return This very builder to allow for method chaining.
*/
public final I withMinWidth( int width ) {
return _with( c -> {
_setMinWidth(c, width);
})
._this();
}
protected final void _setMinWidth( C component, int width ) {
int currentHeight = component.getMinimumSize().height;
if ( !component.isMinimumSizeSet() && UI.currentLookAndFeel().isOneOf(UI.LookAndFeel.METAL, UI.LookAndFeel.NIMBUS) )
currentHeight = UI.scale(currentHeight);
component.setMinimumSize(new Dimension(UI.scale(width), currentHeight));
}
/**
* Use this to dynamically set only the minimum width of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component for you. <br>
* @param width The minimum width which should be set for the underlying component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withMinWidth( Val<Integer> width ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the minimum width of this component!");
return _withOnShow( width, (c,w) -> {
c.setMinimumSize(new Dimension(UI.scale(w), c.getMinimumSize().height));
_revalidate(c); // Swing is not smart enough to do this automatically
})
._with( c -> {
_setMinWidth(c, width.get());
})
._this();
}
/**
* Use this to only set the minimum height of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component for you. <br>
* @param height The minimum height which should be set for the underlying component.
* @return This very builder to allow for method chaining.
*/
public final I withMinHeight( int height ) {
return _with( c -> {
_setMinHeight(c, height);
})
._this();
}
protected final void _setMinHeight( C component, int height ) {
int currentWidth = component.getMinimumSize().width;
if ( !component.isMinimumSizeSet() && UI.currentLookAndFeel().isOneOf(UI.LookAndFeel.METAL, UI.LookAndFeel.NIMBUS) )
currentWidth = UI.scale(currentWidth);
component.setMinimumSize(new Dimension(currentWidth, UI.scale(height)));
}
/**
* Use this to dynamically set only the minimum height of this {@link JComponent}. <br>
* This calls {@link JComponent#setMinimumSize(Dimension)} on the underlying component for you. <br>
* @param height The minimum height which should be set for the underlying component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withMinHeight( Val<Integer> height ) {
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the minimum height of this component!");
return _withOnShow( height, (c,h) -> {
c.setMinimumSize(new Dimension(c.getMinimumSize().width, UI.scale(h)));
_revalidate(c); // Swing is not smart enough to do this automatically
})
._with( c -> {
_setMinHeight(c, height.get());
})
._this();
}
/**
* Set the maximum {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component. <br>
* @param size The maximum {@link Dimension} of the component.
* @return This very builder to allow for method chaining.
*/
public final I withMaxSize( Dimension size ) {
NullUtil.nullArgCheck(size, "size", Dimension.class);
return _with( c -> c.setMaximumSize(UI.scale(size)) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the maximum {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component. <br>
* @param size The maximum {@link Dimension} of the component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withMaxSize( Val<Dimension> size ) {
NullUtil.nullArgCheck(size, "size", Val.class);
NullUtil.nullPropertyCheck(size, "size", "Null is not allowed to model the maximum size of this component!");
return _withOnShow( size, (c,v) -> {
c.setMaximumSize(UI.scale(v));
_revalidate(c); // For some reason this is needed to make the change visible.
})
._with( c -> {
c.setMaximumSize( UI.scale(size.get()) );
})
._this();
}
/**
* Set the maximum width and height ({@link Dimension}) of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component. <br>
* @param width The maximum width of the component.
* @param height The maximum height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withMaxSize( int width, int height ) {
return _with( c -> c.setMaximumSize(new Dimension(UI.scale(width), UI.scale(height))) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the maximum {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component. <br>
* @param width The maximum width of the component.
* @param height The maximum height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withMaxSize( Val<Integer> width, Val<Integer> height ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the maximum width of this component!");
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the maximum height of this component!");
return _withOnShow( width, (c,w) -> {
c.setMaximumSize(new Dimension(UI.scale(w), c.getMaximumSize().height));
_revalidate(c); // Raw Swing is not smart enough to do this automatically :(
})
._withOnShow( height, (c,h) -> {
c.setMaximumSize(new Dimension(c.getMaximumSize().width, UI.scale(h)));
_revalidate(c); // Still not smart enough to do this automatically :(
})
._with( c -> {
c.setMaximumSize( new Dimension(UI.scale(width.get()), UI.scale(height.get())) );
})
._this();
}
/**
* Use this to only set the maximum width of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component for you. <br>
* @param width The maximum width which should be set for the underlying component.
* @return This very builder to allow for method chaining.
*/
public final I withMaxWidth( int width ) {
return _with( c -> {
_setMaxWidth(c, width);
})
._this();
}
private void _setMaxWidth( C component, int width ) {
int currentHeight = component.getMaximumSize().height;
if ( !component.isMaximumSizeSet() && UI.currentLookAndFeel().isOneOf(UI.LookAndFeel.METAL, UI.LookAndFeel.NIMBUS) )
currentHeight = UI.scale(currentHeight);
component.setMaximumSize(new Dimension(UI.scale(width), currentHeight));
}
/**
* Use this to dynamically set only the maximum width of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component for you. <br>
* @param width The maximum width which should be set for the underlying component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withMaxWidth( Val<Integer> width ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the maximum width of this component!");
return _withOnShow( width, (c,w) -> {
c.setMaximumSize(new Dimension(UI.scale(w), c.getMaximumSize().height));
_revalidate(c); // When the size changes, the layout manager needs to be informed.
})
._with( c -> {
_setMaxWidth(c, width.get());
})
._this();
}
/**
* Use this to only set the maximum height of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component for you. <br>
* @param height The maximum height which should be set for the underlying component.
* @return This very builder to allow for method chaining.
*/
public final I withMaxHeight( int height ) {
return _with( c -> {
_setMaxHeight(c, height);
})
._this();
}
private void _setMaxHeight( C component, int height ) {
int currentWidth = component.getMaximumSize().width;
if ( !component.isMaximumSizeSet() && UI.currentLookAndFeel().isOneOf(UI.LookAndFeel.METAL, UI.LookAndFeel.NIMBUS) )
currentWidth = UI.scale(currentWidth);
component.setMaximumSize(new Dimension(currentWidth, UI.scale(height)));
}
/**
* Use this to dynamically set only the maximum height of this {@link JComponent}. <br>
* This calls {@link JComponent#setMaximumSize(Dimension)} on the underlying component for you. <br>
* @param height The maximum height which should be set for the underlying component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withMaxHeight( Val<Integer> height ) {
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the maximum height of this component!");
return _withOnShow( height, (c,h) -> {
c.setMaximumSize(new Dimension(c.getMaximumSize().width, UI.scale(h)));
_revalidate(c); // The revalidate is necessary to make the change visible, this makes sure the layout is recalculated.
})
._with( c -> {
_setMaxHeight(c, height.get());
})
._this();
}
/**
* Set the preferred {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component. <br>
* @param size The preferred {@link Dimension} of the component.
* @return This very builder to allow for method chaining.
*/
public final I withPrefSize( Dimension size ) {
NullUtil.nullArgCheck(size, "size", Dimension.class);
return _with( c -> c.setPreferredSize(UI.scale(size)) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the preferred {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component. <br>
* @param size The preferred {@link Dimension} of the component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withPrefSize( Val<Dimension> size ) {
NullUtil.nullArgCheck(size, "size", Val.class);
NullUtil.nullPropertyCheck(size, "size", "Null is not allowed to model the preferred size of this component!");
return _withOnShow( size, (c,v) -> {
c.setPreferredSize(UI.scale(v));
_revalidate(c);
})
._with( c -> {
c.setPreferredSize( UI.scale(size.get()) );
})
._this();
}
/**
* Set the preferred width and height ({@link Dimension}) of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component. <br>
* @param width The preferred width of the component.
* @param height The preferred height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withPrefSize( int width, int height ) {
return _with( c -> c.setPreferredSize(new Dimension(UI.scale(width), UI.scale(height))) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the preferred {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component. <br>
* @param width The preferred width of the component.
* @param height The preferred height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withPrefSize( Val<Integer> width, Val<Integer> height ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the preferred width of this component!");
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the preferred height of this component!");
return _withOnShow( width, (c,w) -> {
c.setPreferredSize(new Dimension(UI.scale(w), c.getPreferredSize().height));
_revalidate(c); // We need to revalidate the component to make sure the layout manager is aware of the new size.
})
._withOnShow( height, (c,h) -> {
c.setPreferredSize(new Dimension(c.getPreferredSize().width, UI.scale(h)));
_revalidate(c); // We need to revalidate the component to make sure the layout manager is aware of the new size.
})
._with( c -> {
c.setPreferredSize( new Dimension(UI.scale(width.get()), UI.scale(height.get())) );
})
._this();
}
/**
* Use this to only set the preferred width of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component for you. <br>
* @param width The preferred width which should be set for the underlying component.
* @return This very builder to allow for method chaining.
*/
public final I withPrefWidth( int width ) {
return _with( c -> {
_setPrefWidth(c, width);
})
._this();
}
protected final void _setPrefWidth( C component, int width ) {
int currentHeight = component.getPreferredSize().height;
if ( !component.isPreferredSizeSet() && UI.currentLookAndFeel().isOneOf(UI.LookAndFeel.METAL, UI.LookAndFeel.NIMBUS) )
currentHeight = UI.scale(currentHeight);
component.setPreferredSize(new Dimension(UI.scale(width), currentHeight));
}
/**
* Use this to dynamically set only the preferred width of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component for you. <br>
* @param width The preferred width which should be set for the underlying component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withPrefWidth( Val<Integer> width ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the preferred width of this component!");
return _withOnShow( width, (c,w) -> {
c.setPreferredSize(new Dimension(UI.scale(w), c.getPreferredSize().height));
_revalidate(c); // We need to revalidate the component to make sure the new preferred size is applied.
})
._with( c -> {
_setPrefWidth(c, width.get());
})
._this();
}
/**
* Use this to only set the preferred height of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component for you. <br>
* @param height The preferred height which should be set for the underlying component.
* @return This very builder to allow for method chaining.
*/
public final I withPrefHeight( int height ) {
return _with( c -> {
_setPrefHeight(c, height);
})
._this();
}
private void _setPrefHeight( C component, int height ) {
int currentWidth = component.getPreferredSize().width;
if ( !component.isPreferredSizeSet() && UI.currentLookAndFeel().isOneOf(UI.LookAndFeel.METAL, UI.LookAndFeel.NIMBUS) )
currentWidth = UI.scale(currentWidth);
component.setPreferredSize(new Dimension(currentWidth, UI.scale(height)));
}
/**
* Use this to dynamically set only the preferred height of this {@link JComponent}. <br>
* This calls {@link JComponent#setPreferredSize(Dimension)} on the underlying component for you. <br>
* @param height The preferred height which should be set for the underlying component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withPrefHeight( Val<Integer> height ) {
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the preferred height of this component!");
return _withOnShow( height, (c,h) -> {
c.setPreferredSize(new Dimension(c.getPreferredSize().width, UI.scale(h)));
_revalidate(c); // We need to revalidate the component to make sure the new preferred size is applied.
})
._with( c -> {
_setPrefHeight(c, height.get());
})
._this();
}
/**
* Set the current {@link Dimension})/size (width and height) of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param size The current {@link Dimension} of the component.
* @return This very builder to allow for method chaining.
*/
public final I withSize( Dimension size ) {
NullUtil.nullArgCheck(size, "size", Dimension.class);
return _with( c -> c.setSize(UI.scale(size)) )._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the current {@link Dimension} of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param size The current {@link Dimension} of the component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withSize( Val<Dimension> size ) {
NullUtil.nullArgCheck(size, "size", Val.class);
NullUtil.nullPropertyCheck(size, "size", "Null is not allowed to model the size of this component!");
return _withOnShow( size, (c,v) -> {
c.setSize(UI.scale(v));
_revalidate(c); // We need to revalidate the component to make sure the new size is applied.
})
._with( c -> {
c.setSize( UI.scale(size.get()) );
})
._this();
}
/**
* Set the current width and height of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param width The current width of the component.
* @param height The current height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withSize( int width, int height ) {
return this.withSize( new Dimension(width, height) );
}
/**
* Set the current width of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param width The current width of the component.
* @return This very builder to allow for method chaining.
*/
public final I withWidth( int width ) {
return _with( c -> {
c.setSize(new Dimension(UI.scale(width), c.getSize().height));
})
._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the current width of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param width The current width of the component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withWidth( Val<Integer> width ) {
NullUtil.nullArgCheck(width, "width", Val.class);
NullUtil.nullPropertyCheck(width, "width", "Null is not allowed to model the width of this component!");
return _withOnShow( width, (c,w) -> {
c.setSize(new Dimension(UI.scale(w), c.getSize().height));
_revalidate(c); // We need to revalidate the component to make sure the new size is applied.
})
._with( c -> {
c.setSize(new Dimension(UI.scale(width.get()), c.getSize().height));
})
._this();
}
/**
* Set the current height of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param height The current height of the component.
* @return This very builder to allow for method chaining.
*/
public final I withHeight( int height ) {
return _with( c -> {
c.setSize(new Dimension(c.getSize().width, UI.scale(height)));
})
._this();
}
/**
* Bind to a {@link Val} object to
* dynamically set the current height of this {@link JComponent}. <br>
* This calls {@link JComponent#setSize(Dimension)} on the underlying component. <br>
* @param height The current height of the component wrapped by a {@link Val}.
* @return This very builder to allow for method chaining.
*/
public final I withHeight( Val<Integer> height ) {
NullUtil.nullArgCheck(height, "height", Val.class);
NullUtil.nullPropertyCheck(height, "height", "Null is not allowed to model the height of this component!");
return _withOnShow( height, (c,h) -> {
c.setSize(new Dimension(c.getSize().width, UI.scale(h)));
_revalidate(c); // We need to revalidate the component to make sure the new size is applied.
})
._with( c -> {
c.setSize(new Dimension(c.getSize().width, UI.scale(height.get())));
})
._this();
}
private static void _revalidate( Component comp ) {
comp.revalidate();
if ( comp instanceof JScrollPane )
Optional.ofNullable(comp.getParent())
.ifPresent(Component::revalidate); // For some reason, JScrollPane does not revalidate its parent when its preferred size changes.
}
/**
* Calls the provided action event handler when the mouse gets pressed and then released.
* This delegates to a {@link MouseListener} based mouse click event listener registered in the UI component.
* <br><br>
* Note that a click is defined as the combination of the <b>mouse being pressed
* and then released on the same position as it was pressed.</b>
* If the mouse moves between the press and the release events, then the
* event is considered a drag event instead of a mouse click! (see {@link #onMouseDrag(Action)})
*
* @param onClick The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseClick( Action<ComponentMouseEventDelegate<C>> onClick ) {
NullUtil.nullArgCheck(onClick, "onClick", Action.class);
return _with( c -> {
c.addMouseListener(new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) {
_runInApp(() -> onClick.accept(new ComponentMouseEventDelegate<>( c, e )));
}
});
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse release events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
*
* @param onRelease The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseRelease( Action<ComponentMouseEventDelegate<C>> onRelease ) {
NullUtil.nullArgCheck(onRelease, "onRelease", Action.class);
return _with( c -> {
c.addMouseListener(new MouseAdapter() {
@Override public void mouseReleased(MouseEvent e) {
_runInApp(() -> onRelease.accept(new ComponentMouseEventDelegate<>(c, e )));
}
});
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse press events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
*
* @param onPress The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMousePress( Action<ComponentMouseEventDelegate<C>> onPress ) {
NullUtil.nullArgCheck(onPress, "onPress", Action.class);
return _with( c -> {
c.addMouseListener(new MouseAdapter() {
@Override public void mousePressed(MouseEvent e) {
_runInApp(() -> onPress.accept(new ComponentMouseEventDelegate<>(c, e )));
}
});
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse enter events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
*
* @param onEnter The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseEnter( Action<ComponentMouseEventDelegate<C>> onEnter ) {
NullUtil.nullArgCheck(onEnter, "onEnter", Action.class);
return _with( c -> {
c.addMouseListener(new MouseAdapter() {
@Override public void mouseEntered(MouseEvent e) {
_runInApp(() -> onEnter.accept(new ComponentMouseEventDelegate<>(c, e )));
}
});
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse exit events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
*
* @param onExit The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseExit( Action<ComponentMouseEventDelegate<C>> onExit ) {
NullUtil.nullArgCheck(onExit, "onExit", Action.class);
return _with( c -> {
c.addMouseListener(new MouseAdapter() {
@Override public void mouseExited(MouseEvent e) {
_runInApp(() -> onExit.accept(new ComponentMouseEventDelegate<>(c, e )));
}
});
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse drag events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
* The {@link ComponentDragEventDelegate} received by the {@link Action} lambda
* exposes both component and drag event
* context information, including a list of all the {@link MouseEvent}s involved
* in one continuous dragging motion (see {@link ComponentDragEventDelegate#dragEvents()} for more information).
*
* @param onDrag The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseDrag( Action<ComponentDragEventDelegate<C>> onDrag ) {
NullUtil.nullArgCheck(onDrag, "onDrag", Action.class);
return _with( thisComponent -> {
java.util.List<MouseEvent> dragEventHistory = new ArrayList<>();
MouseAdapter listener = new MouseAdapter() {
@Override public void mousePressed(MouseEvent e) {
dragEventHistory.clear();
dragEventHistory.add(e);
}
@Override public void mouseReleased(MouseEvent e) {
dragEventHistory.clear();
}
@Override public void mouseDragged(MouseEvent e) {
dragEventHistory.add(e);
_runInApp(() -> onDrag.accept(new ComponentDragEventDelegate<>(thisComponent, e, dragEventHistory)));
}
};
thisComponent.addMouseListener(listener);
thisComponent.addMouseMotionListener(listener);
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse move events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
*
* @param onMove The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseMove( Action<ComponentMouseEventDelegate<C>> onMove ) {
NullUtil.nullArgCheck(onMove, "onMove", Action.class);
return _with( thisComponent -> {
thisComponent.addMouseListener(new MouseAdapter() {
@Override public void mouseMoved(MouseEvent e) {
_runInApp(() -> onMove.accept(new ComponentMouseEventDelegate<>( thisComponent, e )));
}
});
thisComponent.addMouseMotionListener(new MouseMotionAdapter() {
@Override public void mouseMoved(MouseEvent e) {
_runInApp(() -> onMove.accept(new ComponentMouseEventDelegate<>( thisComponent, e )));
}
});
})
._this();
}
/**
* Use this to register and catch generic {@link MouseListener} based mouse wheel events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
*
* @param onWheel The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseWheelMove( Action<ComponentDelegate<C, MouseWheelEvent>> onWheel ) {
NullUtil.nullArgCheck(onWheel, "onWheel", Action.class);
return _with( thisComponent -> {
thisComponent.addMouseWheelListener( e -> {
_runInApp(() -> onWheel.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
/**
* Use this to register and catch mouse wheel up movement events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
* @param onWheelUp The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseWheelUp( Action<ComponentDelegate<C, MouseWheelEvent>> onWheelUp ) {
NullUtil.nullArgCheck(onWheelUp, "onWheelUp", Action.class);
return _with( thisComponent -> {
thisComponent.addMouseWheelListener( e -> {
if ( e.getWheelRotation() < 0 )
_runInApp(() -> onWheelUp.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
/**
* Use this to register and catch mouse wheel down movement events on this UI component.
* This method adds the provided consumer lambda to
* an an{@link MouseListener} instance to the component.
* <br><br>
* @param onWheelDown The lambda instance which will be passed to the button component as {@link MouseListener}.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMouseWheelDown( Action<ComponentDelegate<C, MouseWheelEvent>> onWheelDown ) {
NullUtil.nullArgCheck(onWheelDown, "onWheelDown", Action.class);
return _with( thisComponent -> {
thisComponent.addMouseWheelListener( e -> {
if ( e.getWheelRotation() > 0 )
_runInApp(() -> onWheelDown.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
/**
* The provided lambda will be invoked when the component's size changes.
* This will internally translate to a {@link ComponentListener} implementation.
* Passing null to this method will cause an exception to be thrown.
*
* @param onResize The resize action which will be called when the underlying component changes size.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onResize( Action<ComponentDelegate<C, ComponentEvent>> onResize ) {
NullUtil.nullArgCheck(onResize, "onResize", Action.class);
return _with( thisComponent -> {
thisComponent.addComponentListener(new ComponentAdapter() {
@Override public void componentResized(ComponentEvent e) {
_runInApp(()->onResize.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* The provided lambda will be invoked when the component was moved.
* This will internally translate to a {@link ComponentListener} implementation.
*
* @param onMoved The action lambda which will be executed once the component was moved / its position canged.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onMoved( Action<ComponentDelegate<C, ComponentEvent>> onMoved ) {
NullUtil.nullArgCheck(onMoved, "onMoved", Action.class);
return _with( thisComponent -> {
thisComponent.addComponentListener(new ComponentAdapter() {
@Override public void componentMoved(ComponentEvent e) {
_runInApp(()->onMoved.accept(new ComponentDelegate<>( thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link ComponentListener}
* to the component, to receive those component events where the wrapped component becomes visible.
*
* @param onShown The {@link Action} which gets invoked when the component has been made visible.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onShown( Action<ComponentDelegate<C, ComponentEvent>> onShown ) {
NullUtil.nullArgCheck(onShown, "onShown", Action.class);
return _with( thisComponent -> {
thisComponent.addComponentListener(new ComponentAdapter() {
@Override public void componentShown(ComponentEvent e) {
_runInApp(()->onShown.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link ComponentListener}
* to the component, to receive those component events where the wrapped component becomes invisible.
*
* @param onHidden The {@link Action} which gets invoked when the component has been made invisible.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onHidden( Action<ComponentDelegate<C, ComponentEvent>> onHidden ) {
NullUtil.nullArgCheck(onHidden, "onHidden", Action.class);
return _with( thisComponent -> {
thisComponent.addComponentListener(new ComponentAdapter() {
@Override public void componentHidden(ComponentEvent e) {
_runInApp(()->onHidden.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link FocusListener}
* to the component, to receive those focus events where the wrapped component gains input focus.
*
* @param onFocus The {@link Action} which should be executed once the input focus was gained on the wrapped component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onFocusGain( Action<ComponentDelegate<C, ComponentEvent>> onFocus ) {
NullUtil.nullArgCheck(onFocus, "onFocus", Action.class);
return _with( thisComponent -> {
thisComponent.addFocusListener(new FocusAdapter() {
@Override public void focusGained(FocusEvent e) {
_runInApp(()->onFocus.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a focus listener
* to receive those focus events where the wrapped component loses input focus.
*
* @param onFocus The {@link Action} which should be executed once the input focus was lost on the wrapped component.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onFocusLoss( Action<ComponentDelegate<C, ComponentEvent>> onFocus ) {
NullUtil.nullArgCheck(onFocus, "onFocus", Action.class);
return _with( thisComponent -> {
thisComponent.addFocusListener(new FocusAdapter() {
@Override public void focusLost(FocusEvent e) {
_runInApp(()->onFocus.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener}
* to the component, to receive key events triggered when the wrapped component receives keyboard input.
* <br><br>
* @param onKeyPressed The {@link Action} which will be executed once the wrapped component received a key press.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onKeyPress( Action<ComponentDelegate<C, KeyEvent>> onKeyPressed ) {
NullUtil.nullArgCheck(onKeyPressed, "onKeyPressed", Action.class);
return _with( thisComponent -> {
thisComponent.addKeyListener(new KeyAdapter() {
@Override public void keyPressed(KeyEvent e) {
_runInApp(()->onKeyPressed.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener} to the component,
* to receive key events triggered when the wrapped component receives a particular
* keyboard input matching the provided {@link swingtree.input.Keyboard.Key}.
* <br><br>
* @param key The {@link swingtree.input.Keyboard.Key} which should be matched to the key event.
* @param onKeyPressed The {@link Action} which will be executed once the wrapped component received the targeted key press.
* @return This very instance, which enables builder-style method chaining.
*/
public final I onPressed( Keyboard.Key key, Action<ComponentDelegate<C, KeyEvent>> onKeyPressed ) {
NullUtil.nullArgCheck(key, "key", Keyboard.Key.class);
NullUtil.nullArgCheck(onKeyPressed, "onKeyPressed", Action.class);
return _with( thisComponent -> {
thisComponent.addKeyListener(new KeyAdapter() {
@Override public void keyPressed( KeyEvent e ) {
if ( e.getKeyCode() == key.code )
_runInApp(()->onKeyPressed.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener}
* to the component, to receive key events triggered when the wrapped component receives keyboard input.
* <br><br>
* @param onKeyReleased The {@link Action} which will be executed once the wrapped component received a key release.
* @return This very instance, which enables builder-style method chaining.
* @see #onKeyPress(Action)
*/
public final I onKeyRelease( Action<ComponentDelegate<C, KeyEvent>> onKeyReleased ) {
NullUtil.nullArgCheck(onKeyReleased, "onKeyReleased", Action.class);
return _with( thisComponent -> {
thisComponent.addKeyListener(new KeyAdapter() {
@Override public void keyReleased(KeyEvent e) {
_runInApp(()->onKeyReleased.accept(new ComponentDelegate<>(thisComponent, e ))); }
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener} to the component,
* to receive key events triggered when the wrapped component receives a particular
* keyboard input matching the provided {@link swingtree.input.Keyboard.Key}.
* <br><br>
* @param key The {@link swingtree.input.Keyboard.Key} which should be matched to the key event.
* @param onKeyReleased The {@link Action} which will be executed once the wrapped component received the targeted key release.
* @return This very instance, which enables builder-style method chaining.
* @see #onKeyPress(Action)
* @see #onKeyRelease(Action)
*/
public final I onRelease( Keyboard.Key key, Action<ComponentDelegate<C, KeyEvent>> onKeyReleased ) {
NullUtil.nullArgCheck(key, "key", Keyboard.Key.class);
NullUtil.nullArgCheck(onKeyReleased, "onKeyReleased", Action.class);
return _with( thisComponent -> {
thisComponent.addKeyListener(new KeyAdapter() {
@Override public void keyReleased( KeyEvent e ) {
if ( e.getKeyCode() == key.code )
_runInApp(()->onKeyReleased.accept(new ComponentDelegate<>(thisComponent, e )));
}
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener}
* to the component, to receive key events triggered when the wrapped component receives keyboard input.
* <br><br>
* @param onKeyTyped The {@link Action} which will be executed once the wrapped component received a key typed.
* @return This very instance, which enables builder-style method chaining.
* @see #onKeyPress(Action)
* @see #onKeyRelease(Action)
*/
public final I onKeyTyped( Action<ComponentDelegate<C, KeyEvent>> onKeyTyped ) {
NullUtil.nullArgCheck(onKeyTyped, "onKeyTyped", Action.class);
return _with( thisComponent -> {
_onKeyTyped(thisComponent, (e, kl) -> {
_runInApp(() -> onKeyTyped.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener} to the component,
* to receive key events triggered when the wrapped component receives a particular
* keyboard input matching the provided {@link swingtree.input.Keyboard.Key}.
* <br><br>
* @param key The {@link swingtree.input.Keyboard.Key} which should be matched to the key event.
* @param onKeyTyped The {@link Action} which will be executed once the wrapped component received the targeted key typed.
* @return This very instance, which enables builder-style method chaining.
* @see #onKeyPress(Action)
* @see #onKeyRelease(Action)
* @see #onKeyTyped(Action)
*/
public final I onTyped( Keyboard.Key key, Action<ComponentDelegate<C, KeyEvent>> onKeyTyped ) {
NullUtil.nullArgCheck(key, "key", Keyboard.Key.class);
NullUtil.nullArgCheck(onKeyTyped, "onKeyTyped", Action.class);
return _with( thisComponent -> {
_onKeyTyped(thisComponent, (e, kl) -> {
if ( e.getKeyCode() == key.code )
_runInApp(()->onKeyTyped.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
private void _onKeyTyped(C component, BiConsumer<KeyEvent, KeyAdapter> action ) {
component.addKeyListener(new KeyAdapter() {
private @Nullable KeyEvent lastEvent;
@Override
public void keyPressed(KeyEvent e) {
lastEvent = e;
}
@Override
public void keyReleased(KeyEvent e) {
if ( lastEvent != null && lastEvent.getKeyCode() == e.getKeyCode() ) {
action.accept(lastEvent, this);
lastEvent = null;
}
}
});
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener} to the component,
* to receive key events triggered when the wrapped component receives a particular
* keyboard input matching the provided character. <br>
* This method is a logical extension of the {@link #onTyped(Keyboard.Key, Action)} method,
* with the difference that it listens for any character instead of a specific key code.
* This also works with special characters which are typed using the combination
* of multiple keys (e.g. shift + number keys).
* <br><br>
* @param character The character to listen for.
* @param onKeyTyped The action to execute when the character is typed.
* @return This very instance, which enables builder-style method chaining.
* @see #onKeyTyped(Action)
* @see #onTyped(Keyboard.Key, Action)
*/
public final I onTyped( char character, Action<ComponentDelegate<C, KeyEvent>> onKeyTyped ) {
NullUtil.nullArgCheck(onKeyTyped, "onKeyTyped", Action.class);
return _with( thisComponent -> {
_onCharTyped(thisComponent, (e, kl) -> {
if ( e.getKeyChar() == character )
_runInApp(()->onKeyTyped.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
/**
* Adds the supplied {@link Action} wrapped in a {@link KeyListener} to the component,
* to receive key events triggered when the wrapped component receives a particular
* keyboard input matching the provided character. <br>
* This method is a logical extension of the {@link #onTyped(Keyboard.Key, Action)} method,
* with the difference that it listens for any character instead of a specific key code.
* This also works with special characters which are typed using the combination
* of multiple keys (e.g. shift + number keys).
* <br><br>
* @param onKeyTyped The action to execute when the character is typed.
* @return This very instance, which enables builder-style method chaining.
* @see #onKeyTyped(Action)
* @see #onKeyTyped(Action)
* @see #onTyped(Keyboard.Key, Action)
*/
public final I onCharTyped( Action<ComponentDelegate<C, KeyEvent>> onKeyTyped ) {
NullUtil.nullArgCheck(onKeyTyped, "onKeyTyped", Action.class);
return _with( thisComponent -> {
_onCharTyped(thisComponent, (e, kl) -> {
_runInApp(()->onKeyTyped.accept(new ComponentDelegate<>(thisComponent, e )));
});
})
._this();
}
private void _onCharTyped(C component, BiConsumer<KeyEvent, KeyAdapter> action ) {
component.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
action.accept(e, this);
}
});
}
/**
* Allows you to cause an effect inside your UI when an observable event is fired.
* The provided {@link Action} event handler will be called
* on the UI thread when the {@link Observable} event is fired, irrespective of
* what thread the {@link Observable} event is fired on.
* However, it is expected that the {@link Observable} event is fired on the application thread
* and <b>the concrete implementation of the {@link Observable} is intended to
* be part of your view model</b>.
* <br><br>
* Here an example:
* <pre>{@code
* UI.label("I have a color animation!")
* .on(viewModel.someEvent(), it ->
* it.animateFor(3, TimeUnit.SECONDS, state -> {
* double r = state.progress();
* double g = 1 - state.progress();
* double b = state.pulse();
* it.setBackgroundColor(r, g, b);
* })
* )
* }</pre>
*
* @param observableEvent The {@link Observable} event to which the {@link Action} should be attached
* and called on the UI thread when the event is fired in the view model.
* @param action The {@link Action} which is invoked by the UI thread after the {@link Observable} event was fired
* by the business logic of the view model.
* @return This very instance, which enables builder-style method chaining.
* @param <E> The type of the {@link Observable} event.
*/
public final <E extends Observable> I onView( E observableEvent, Action<ComponentDelegate<C, E>> action ) {
NullUtil.nullArgCheck(observableEvent, "observableEvent", Observable.class);
NullUtil.nullArgCheck(action, "action", Action.class);
return _with( thisComponent -> {
observableEvent.subscribe(() -> {
_runInUI(() -> action.accept(new ComponentDelegate<>(thisComponent, observableEvent )));
});
})
._this(); }
/**
* Use this to attach a component {@link Action} event handler to a functionally supplied
* {@link Observable} event in order to implement a custom user event system.
* The supplied {@link Action} is executed on the application thread when the {@link Observable} event is fired and
* irrespective of the thread that {@link Observable} fired the event. <br>
* The {@link Action} is expected to perform an effect on the view model or the application state,
* <b>but not on the UI directly</b>. <br>
* (see {@link #onView(Observable, Action)} if you want your view model to affect the UI through an observable event)
* <br><br>
* Consider the following example:
* <pre>{@code
* UI.label("")
* .on(CustomEventSystem.touchGesture(), it -> ..some App update.. )
* }</pre>
* In this example we use an imaginary {@code CustomEventSystem} to register a touch gesture event handler
* which will be called on the application thread when the touch gesture event is fired.
* Although neither Swing nor SwingTree have a touch gesture event system, this example illustrates
* how one could easily integrate a custom event system into SwingTree UIs.
* <br><br>
* <b>
* Note that the provided {@link Observable} event is NOT expected to be part of the view model,
* but rather part of a custom event system that captures user input or other input
* which is not directly related to the business logic of the view model.
* </b>
*
* @param observableEvent The {@link Observable} event to which the {@link Action} should be attached.
* @param action The {@link Action} which is invoked by the application thread after the {@link Observable} event was fired.
* @return This very instance, which enables builder-style method chaining.
* @param <E> The type of the {@link Observable} event.
* @see #onView(Observable, Action) for a similar method which is intended to be used with view model events.
*/
public final <E extends Observable> I on( E observableEvent, Action<ComponentDelegate<C, E>> action ) {
NullUtil.nullArgCheck(observableEvent, "observableEvent", Observable.class);
NullUtil.nullArgCheck(action, "action", Action.class);
return _with( thisComponent -> {
observableEvent.subscribe(() -> {
_runInApp(() -> action.accept(new ComponentDelegate<>(thisComponent, observableEvent )));
});
})
._this();
}
/**
* This is a logical extension of the {@link #on(Observable, Action)} method.
* Use this to attach a component {@link Action} event handler to a functionally supplied
* {@link Observable} event.
* The {@link Action} will be called on the application thread when the {@link Observable} event
* is fired, irrespective of the thread that fired the {@link Observable} event.
* The {@link Action} is expected to perform an effect on the view model or the application state,
* <b>but not on the UI directly</b>. <br>
* (see {@link #onView(Observable, Action)} if you want your view model to affect the UI through an observable event)
* <br><br>
* Consider the following example:
* <pre>{@code
* UI.label("")
* .on(c -> CustomEventSystem.touchGesture(c), it -> ..some App update.. )
* }</pre>
* Which may also be written as:
* <pre>{@code
* UI.label("")
* .on(CustomEventSystem::touchGesture, it -> ..some App update.. )
* }</pre>
* In this example we use an imaginary {@code CustomEventSystem} to register a component specific
* touch gesture event handler which will be called on the application thread when the touch gesture event is fired.
* Although neither Swing nor SwingTree have a touch gesture event system, this example illustrates
* how one could easily integrate a custom event system into SwingTree UIs.
* <br><br>
* <b>
* Note that the {@link Observable} event supplied by the function
* is NOT expected to be part of the view model,
* but rather be part of a custom event system that captures user input or other input
* which is not directly related to the business logic of the view model.
* </b>
*
* @param eventSource The {@link Observable} event to which the {@link Action} should be attached.
* @param action The {@link Action} which is invoked by the application thread after the {@link Observable} event was fired.
* @return This very instance, which enables builder-style method chaining.
* @param <E> The type of the {@link Observable} event.
* @see #onView(Observable, Action) for a similar method which is intended to be used with view model events.
*/
public final <E extends Observable> I on( Function<C, E> eventSource, Action<ComponentDelegate<C, E>> action ) {
NullUtil.nullArgCheck(eventSource, "eventSource", Function.class);
NullUtil.nullArgCheck(action, "action", Action.class);
return _with( thisComponent -> {
E observableEvent = eventSource.apply(thisComponent);
observableEvent.subscribe(() -> {
_runInApp(() -> action.accept(new ComponentDelegate<>( thisComponent, observableEvent )));
});
})
._this();
}
/**
* Use this to register periodic update actions which should be called
* based on the provided {@code delay}! <br>
* The following example produces a label which will display the current date.
* <pre>{@code
* UI.label("")
* .doUpdates( 100, it -> it.getComponent().setText(new Date().toString()) )
* }</pre>
*
* @param delay The delay in milliseconds between calling the provided {@link Action}.
* @param onUpdate The {@link Action} which should be called periodically.
* @return This very instance, which enables builder-style method chaining.
*/
public final I doUpdates( int delay, Action<ComponentDelegate<C, ActionEvent>> onUpdate ) {
NullUtil.nullArgCheck(onUpdate, "onUpdate", Action.class);
return _with( thisComponent -> {
Timer timer = new Timer(delay, e -> onUpdate.accept(new ComponentDelegate<>(thisComponent, e )));
{
java.util.List<Timer> timers = (java.util.List<Timer>) thisComponent.getClientProperty(_TIMERS_KEY);
if ( timers == null ) {
timers = new ArrayList<>();
thisComponent.putClientProperty(_TIMERS_KEY, timers);
}
timers.add(timer);
}
timer.start();
})
._this();
}
@Override
protected void _addComponentTo(
C thisComponent,
JComponent addedComponent,
@Nullable Object constraints
) {
NullUtil.nullArgCheck(addedComponent, "component", JComponent.class);
if ( constraints == null )
thisComponent.add( addedComponent );
else
thisComponent.add( addedComponent, constraints );
}
/**
* Use this to nest builder nodes into this builder to effectively plug the wrapped {@link JComponent}s
* into the {@link JComponent} type wrapped by this builder instance.
* The first argument is expected to contain layout information for the layout manager of the wrapped {@link JComponent},
* through the {@link JComponent#add(Component, Object)} method.
* By default, the {@link MigLayout} is used.
* <br><br>
*
* @param attr The additional mig-layout information which should be passed to the UI tree.
* @param builder A builder for another {@link JComponent} instance which ought to be added to the wrapped component type.
* @param <T> The type of the {@link JComponent} which is wrapped by the provided builder.
* @return This very instance, which enables builder-style method chaining.
*/
public final <T extends JComponent> I add( String attr, UIForAnySwing<?, T> builder ) {
return this.add(attr, new UIForAnySwing[]{builder});
}
/**
* Use this to nest builder nodes into this builder to effectively plug the wrapped {@link JComponent}s
* into the {@link JComponent} type wrapped by this builder instance.
* The first argument will be passed to the layout manager of the wrapped {@link JComponent},
* through the {@link JComponent#add(Component, Object)} method.
* By default, the {@link MigLayout} is used.
* <br><br>
*
* @param attr The mig-layout attribute.
* @param builder A builder for another {@link JComponent} instance which ought to be added to the wrapped component type.
* @param <T> The type of the {@link JComponent} which is wrapped by the provided builder.
* @return This very instance, which enables builder-style method chaining.
*/
public final <T extends JComponent> I add( AddConstraint attr, UIForAnySwing<?, T> builder ) {
return this.add(attr.toString(), new UIForAnySwing[]{builder});
}
/**
* Use this to nest builder types into this builder to effectively plug the wrapped {@link JComponent}s
* into the {@link JComponent} type wrapped by this builder instance.
* The first argument represents layout attributes/constraints which will
* be passed to the {@link LayoutManager} of the underlying {@link JComponent}.
* through the {@link JComponent#add(Component, Object)} method.
* <br><br>
* This may look like this:
* <pre>{@code
* UI.panel()
* .add("wrap", UI.label("A"), UI.label("B"))
* .add("grow", UI.label("C"), UI.label("D"))
* }</pre>
* Note that the first argument, "wrap" and "grow" in this case, are
* used as layout constraints for all the {@link JComponent}s which are added
* in the subsequent arguments of a single call to this method.
*
*
* @param attr The additional mig-layout information which should be passed to the UI tree.
* @param builders An array of builders for a corresponding number of {@link JComponent}
* type which ought to be added to the wrapped component type of this builder.
* @param <B> The builder type parameter, a subtype of {@link UIForAnySwing}.
* @return This very instance, which enables builder-style method chaining.
*/
@SafeVarargs
public final <B extends UIForAnySwing<?, ?>> I add( String attr, B... builders ) {
return _with( thisComponent -> {
_addBuildersTo(thisComponent, attr, builders);
})
._this();
}
private void _addBuildersTo( C thisComponent, String attr, UIForAnySwing<?, ?>... builders ) {
LayoutManager layout = thisComponent.getLayout();
if ( _isBorderLayout(attr) && !(layout instanceof BorderLayout) ) {
if ( layout instanceof MigLayout )
log.warn("Layout ambiguity detected! Border layout constraint cannot be added to 'MigLayout'.");
thisComponent.setLayout(new BorderLayout()); // The UI Maker tries to fill in the blanks!
}
for ( UIForAnySwing<?, ?> b : builders )
_addBuilderTo(thisComponent, b, attr);
}
/**
* Use this to nest builder types into this builder to effectively plug the wrapped {@link JComponent}s
* into the {@link JComponent} type wrapped by this builder instance.
* The first argument will be passed to the {@link LayoutManager}
* of the underlying {@link JComponent} to serve as layout constraints
* through the {@link JComponent#add(Component, Object)} method.
* <br><br>
*
* @param attr The first mig-layout information which should be passed to the UI tree.
* @param builders An array of builders for a corresponding number of {@link JComponent}
* type which ought to be added to the wrapped component type of this builder.
* @param <B> The builder type parameter, a subtype of {@link UIForAnySwing}.
* @return This very instance, which enables builder-style method chaining.
*/
@SafeVarargs
public final <B extends UIForAnySwing<?, ?>> I add( AddConstraint attr, B... builders ) {
Objects.requireNonNull(attr, "attr");
Objects.requireNonNull(builders, "builders");
return this.add( attr.toString(), builders );
}
/**
* Use this to nest builder types into this builder to effectively plug the {@link JComponent}s
* wrapped by the provided builders
* into the {@link JComponent} type wrapped by this builder instance.
* The first argument represents placement constraints for the provided components which will
* be passed to the {@link MigLayout} of the underlying {@link JComponent}
* through the {@link JComponent#add(Component, Object)} method.
* <br><br>
*
* @param attr The additional mig-layout information which should be passed to the UI tree.
* @param builders An array of builders for a corresponding number of {@link JComponent}
* type which ought to be added to the wrapped component type of this builder.
* @param <B> The builder type parameter, a subtype of {@link UIForAnySwing}.
* @return This very instance, which enables builder-style method chaining.
*/
@SafeVarargs
public final <B extends UIForAnySwing<?, ?>> I add( CC attr, B... builders ) {
Objects.requireNonNull(attr, "attr");
Objects.requireNonNull(builders, "builders");
return _with( thisComponent -> {
LayoutManager layout = thisComponent.getLayout();
if ( !(layout instanceof MigLayout) )
log.warn("Layout ambiguity detected! Mig layout constraint cannot be added to '{}'.", layout.getClass().getSimpleName());
for ( UIForAnySwing<?, ?> b : builders )
_addBuilderTo(thisComponent, b, attr);
})
._this();
}
/**
* Use this to nest {@link JComponent} types into this builder to effectively plug the provided {@link JComponent}s
* into the {@link JComponent} type wrapped by this builder instance.
* The first argument represents layout attributes/constraints which will
* be applied to the subsequently provided {@link JComponent} types.
* <br><br>
* This may look like this:
* <pre>{@code
* UI.panel()
* .add("wrap", new JLabel("A"), new JLabel("B"))
* .add("grow", new JLabel("C"), new JLabel("D"))
* }</pre>
* Note that the first argument, "wrap" and "grow" in this case, are
* used as layout constraints for all the {@link JComponent}s which are added
* in the subsequent arguments of a single call to this method.
*
* @param attr The additional layout information which should be passed to the UI tree.
* @param components A {@link JComponent}s array which ought to be added to the wrapped component type.
* @return This very instance, which enables builder-style method chaining.
* @param <E> The type of the {@link JComponent} which is wrapped by the provided builder.
*/
@SafeVarargs
public final <E extends JComponent> I add( String attr, E... components ) {
NullUtil.nullArgCheck(attr, "attr", String.class);
NullUtil.nullArgCheck(components, "components", JComponent[].class);
return _with( thisComponent -> {
_addComponentsTo( thisComponent, attr, components );
})
._this();
}
@SafeVarargs
private final <E extends JComponent> void _addComponentsTo( C thisComponent, String attr, E... components ) {
for ( E component : components ) {
NullUtil.nullArgCheck(component, "component", JComponent.class);
_addBuildersTo( thisComponent, attr, new UIForSwing[]{UI.of(component)} );
}
}
/**
* Use this to nest {@link JComponent} types into this builder to effectively plug the provided {@link JComponent}s
* into the {@link JComponent} type wrapped by this builder instance.
* The first 2 arguments will be joined by a comma and passed to the {@link LayoutManager}
* of the underlying {@link JComponent} to serve as layout constraints.
* <br><br>
*
* @param attr The first layout information which should be passed to the UI tree.
* @param components A {@link JComponent}s array which ought to be added to the wrapped component type.
* @return This very instance, which enables builder-style method chaining.
* @param <E> The type of the {@link JComponent} which is wrapped by the provided builder.
*/
@SafeVarargs
public final <E extends JComponent> I add( AddConstraint attr, E... components ) {
return this.add(attr.toString(), components);
}
/**
* This allows you to dynamically generate a view for the item of a property (usually a property
* holding a sub-view model) and automatically regenerate the view when the property changes.
* The {@link ViewSupplier} lambda passed to this method will receive the value of the property
* and is then expected to return a {@link JComponent} instance which will be added to the
* wrapped {@link JComponent} type of this builder.
*
* @param viewable A {@link sprouts.Val} property holding null or any other type of value,
* preferably a view model instance.
* @param viewSupplier A {@link ViewSupplier} instance which will be used to generate the view for the value held by the property.
* @return This very instance, which enables builder-style method chaining.
* @param <M> The type of the value held by the {@link Val} property.
*/
public final <M> I add( Val<M> viewable, ViewSupplier<M> viewSupplier ) {
NullUtil.nullArgCheck(viewable, "viewable", Val.class);
return _with( thisComponent -> {
_addViewablePropTo(thisComponent, viewable, null, viewSupplier);
})
._this();
}
/**
* This allows you to dynamically generate views for the items in a {@link Vals} property list
* and automatically regenerate the view when any of the items change.
* The type of item can be anything, but it is usually a view model instance.
* The {@link ViewSupplier} lambda passed to this method will receive the value of the property
* and is then expected to return a {@link JComponent} instance which will be added to the
* wrapped {@link JComponent} type of this builder.
*
* @param viewables A {@link sprouts.Vals} list of items of any type but preferably view model instances.
* @param viewSupplier A {@link ViewSupplier} instance which will be used to generate the view for each item in the list.
* The views will be added to the component wrapped by this builder instance.
* @return This very instance, which enables builder-style method chaining.
* @param <M> The type of the items in the {@link Vals} list.
*/
public final <M> I add( Vals<M> viewables, ViewSupplier<M> viewSupplier ) {
NullUtil.nullArgCheck(viewables, "viewables", Vals.class);
return _with( thisComponent -> {
_addViewableProps( viewables, null, viewSupplier, thisComponent );
})
._this();
}
/**
* This allows you to dynamically generate a view for the item of a property (usually a property
* holding a sub-view model) and automatically regenerate the view when the property changes.
* The {@link ViewSupplier} lambda passed to this method will receive the value of the property
* and is then expected to return a {@link JComponent} instance which will be added to the
* wrapped {@link JComponent} type of this builder.
*
* @param attr The layout information which should be used as layout constraints for the generated view.
* @param viewable A {@link sprouts.Val} property holding null or any other type of value,
* preferably a view model instance.
* @param viewSupplier A {@link ViewSupplier} instance which will be used to generate the view for the value held by the property.
* @return This very instance, which enables builder-style method chaining.
* @param <M> The type of the value held by the {@link Val} property.
*/
public final <M> I add( String attr, Val<M> viewable, ViewSupplier<M> viewSupplier ) {
NullUtil.nullArgCheck(attr, "attr", Object.class);
NullUtil.nullArgCheck(viewable, "viewable", Val.class);
return _with( thisComponent -> {
_addViewablePropTo(thisComponent, viewable, attr, viewSupplier);
})
._this();
}
/**
* This allows you to dynamically generate views for the items in a {@link Vals} property list
* and automatically regenerate the view when any of the items change.
* The type of item can be anything, but it is usually a view model instance.
* The {@link ViewSupplier} lambda passed to this method will receive the value of the property
* and is then expected to return a {@link JComponent} instance which will be added to the
* wrapped {@link JComponent} type of this builder.
*
* @param attr The layout information which should be used as layout constraints for the generated views.
* @param viewables A {@link sprouts.Vals} list of items of any type but preferably view model instances.
* @param viewSupplier A {@link ViewSupplier} instance which will be used to generate the view for each item in the list.
* The views will be added to the component wrapped by this builder instance.
* @return This very instance, which enables builder-style method chaining.
* @param <M> The type of the items in the {@link Vals} list.
*/
public final <M> I add( String attr, Vals<M> viewables, ViewSupplier<M> viewSupplier ) {
NullUtil.nullArgCheck(attr, "attr", Object.class);
NullUtil.nullArgCheck(viewables, "viewables", Vals.class);
return _with( thisComponent -> {
_addViewableProps( viewables, attr, viewSupplier, thisComponent );
})
._this();
}
/**
* This allows you to dynamically generate a view for the item of a property (usually a property
* holding a sub-view model) and automatically regenerate the view when the property changes.
* The {@link ViewSupplier} lambda passed to this method will receive the value of the property
* and is then expected to return a {@link JComponent} instance which will be added to the
* wrapped {@link JComponent} type of this builder.
*
* @param attr The layout information which should be used as layout constraints for the generated view.
* @param viewable A {@link sprouts.Val} property holding null or any other type of value,
* preferably a view model instance.
* @param viewSupplier A {@link ViewSupplier} instance which will be used to generate the view for the value held by the property.
* @return This very instance, which enables builder-style method chaining.
* @param <M> The type of the value held by the {@link Val} property.
*/
public final <M> I add( AddConstraint attr, Val<M> viewable, ViewSupplier<M> viewSupplier ) {
return this.add(attr.toString(), viewable, viewSupplier);
}
/**
* This allows you to dynamically generate views for the items in a {@link Vals} property list
* and automatically regenerate the view when any of the items change.
* The type of item can be anything, but it is usually a view model instance.
* The {@link ViewSupplier} lambda passed to this method will receive the value of the property
* and is then expected to return a {@link JComponent} instance which will be added to the
* wrapped {@link JComponent} type of this builder.
*
* @param attr The layout information which should be used as layout constraints for the generated views.
* @param viewables A {@link sprouts.Vals} list of items of any type but preferably view model instances.
* @param viewSupplier A {@link ViewSupplier} instance which will be used to generate the view for each item in the list.
* The views will be added to the component wrapped by this builder instance.
* @return This very instance, which enables builder-style method chaining.
* @param <M> The type of the items in the {@link Vals} list.
*/
public final <M> I add( AddConstraint attr, Vals<M> viewables, ViewSupplier<M> viewSupplier ) {
return this.add(attr.toString(), viewables, viewSupplier);
}
protected <M> void _addViewableProps( Vals<M> models, @Nullable String attr, ViewSupplier<M> viewSupplier, C thisComponent ) {
_onShow( models, thisComponent, (c, delegate) -> {
// we simply redo all the components.
Vals<M> newValues = delegate.newValues();
Vals<M> oldValues = delegate.oldValues();
switch ( delegate.changeType() ) {
case SET:
for ( int i = 0; i < newValues.size(); i++ ) {
int position = i + delegate.index();
_updateComponentAt(position, newValues.at(i).get(), viewSupplier, attr, c);
}
break;
case ADD:
if ( delegate.index() < 0 && newValues.any(Val::isEmpty) ) {
// This is basically a add all operation, so we clear the components first.
_clearComponentsOf(c);
// and then we add all the components.
for ( int i = 0; i < delegate.vals().size(); i++ )
_addComponentAt( i, delegate.vals().at(i).get(), viewSupplier, attr, c );
}
else {
for ( int i = 0; i < newValues.size(); i++ ) {
int position = i + delegate.index();
_addComponentAt(position, newValues.at(i).get(), viewSupplier, attr, c);
}
}
break;
case REMOVE:
for ( int i = oldValues.size() - 1; i >= 0; i-- ) {
int position = i + delegate.index();
_removeComponentAt(position, c);
}
break;
case CLEAR: _clearComponentsOf(c); break;
case NONE: break;
default: throw new IllegalStateException("Unknown type: "+delegate.changeType());
}
if ( c.getComponentCount() != delegate.vals().size() )
log.warn(
"Broken binding to view model list detected! \n" +
"UI sub-component count '"+c.getComponentCount()+"' " +
"does not match viewable models list of size '"+delegate.vals().size()+"'. \n" +
"A possible cause for this is that components " +
"were " + ( c.getComponentCount() > delegate.vals().size() ? "added" : "removed" ) + " " +
"to this '" + c + "' \ndirectly, instead of through the property list binding. \n" +
"However, this could also be a bug in the UI framework.",
new Throwable()
);
});
models.forEach( v -> {
if ( attr == null )
_addBuildersTo( thisComponent, viewSupplier.createViewFor(v) );
else
_addBuildersTo( thisComponent, attr, viewSupplier.createViewFor(v) );
});
}
private <M> void _addViewablePropTo(
C thisComponent, Val<M> viewable, @Nullable String attr, ViewSupplier<M> viewSupplier
) {
// First we remember the index of the component which will be provided by the viewable dynamically.
final int index = thisComponent.getComponentCount();
// Then we add the component provided by the viewable to the list of children.
if ( attr == null ) {
if ( viewable.isPresent() )
_addBuildersTo(thisComponent, viewSupplier.createViewFor(viewable.get()));
else
_addComponentsTo(thisComponent, new JPanel()); // We add a dummy component to the list of children.
} else {
if ( viewable.isPresent() )
_addBuildersTo(thisComponent, attr, viewSupplier.createViewFor(viewable.get()));
else
_addComponentsTo(thisComponent, attr, new JPanel()); // We add a dummy component to the list of children.
}
// Finally we add a listener to the viewable which will update the component when the viewable changes.
_onShow( viewable, thisComponent, (c,v) -> _updateComponentAt(index, v, viewSupplier, attr, c) );
}
private <M> void _updateComponentAt(
int index, @Nullable M v, ViewSupplier<M> viewSupplier, @Nullable String attr, C c
) {
JComponent newComponent = v == null ? new JPanel() : UI.use(_state().eventProcessor(), () -> viewSupplier.createViewFor(v).getComponent() );
// We remove the old component.
c.remove(c.getComponent(index));
// We add the new component.
if ( attr == null )
c.add(newComponent, index);
else
c.add(newComponent, attr, index);
// We update the layout.
c.revalidate();
c.repaint();
}
private <M> void _addComponentAt(
int index, M v, ViewSupplier<M> viewSupplier, @Nullable String attr, C thisComponent
) {
// We add the new component.
if ( attr == null )
thisComponent.add(UI.use(_state().eventProcessor(), () -> viewSupplier.createViewFor(v).getComponent()), index);
else
thisComponent.add(UI.use(_state().eventProcessor(), () -> viewSupplier.createViewFor(v).getComponent()), attr, index);
// We update the layout.
thisComponent.revalidate();
thisComponent.repaint();
}
private void _removeComponentAt( int index, C thisComponent )
{
if ( index < 0 ) {
log.error(
"Cannot remove sub-component of '"+thisComponent+"' \n" +
"at index '"+index+"' because the index is negative.",
new Throwable()
);
} else {
int numberOfExistingComponents = thisComponent.getComponentCount();
if (index >= numberOfExistingComponents) {
log.error(
"Cannot remove sub-component of '" + thisComponent + "' \n" +
"at index '" + index + "' because there it currently only has '" + numberOfExistingComponents + "' " +
"sub-components instead of at least '" + (index + 1) + "' sub-components.",
new Throwable()
);
} else {
// We get the component at the specified index.
Component component = thisComponent.getComponent(index);
if ( component == null ) {
log.error(
"Cannot remove sub-component of '" + thisComponent + "' \n" +
"at index '" + index + "' because there is no component at that index.",
new Throwable()
);
} else {
// We remove the component.
thisComponent.remove(component);
// We update the layout.
thisComponent.revalidate();
thisComponent.repaint();
}
}
}
}
private void _clearComponentsOf( C thisComponent ) {
// We remove all components.
thisComponent.removeAll();
// We update the layout.
thisComponent.revalidate();
thisComponent.repaint();
}
private static boolean _isBorderLayout( Object o ) {
return BorderLayout.CENTER.equals(o) ||
BorderLayout.PAGE_START.equals(o) ||
BorderLayout.PAGE_END.equals(o) ||
BorderLayout.LINE_END.equals(o) ||
BorderLayout.LINE_START.equals(o) ||
BorderLayout.EAST.equals(o) ||
BorderLayout.WEST.equals(o) ||
BorderLayout.NORTH.equals(o) ||
BorderLayout.SOUTH.equals(o);
}
}