Skip to content

Commit

Permalink
Improve preferences grid layout (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
edreed authored Feb 26, 2024
1 parent f23cfd0 commit 1aad77f
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ private static Optional<Reflections> loadFromMetadata() {
}
} catch (Exception e) {
System.err.println("WARNING: Failed to load Reflections metadata: " + e.getMessage());
reflections = null;
}

return reflections;
Expand Down
2 changes: 1 addition & 1 deletion nrgcommon/src/main/java/com/nrg948/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ of this software and associated documentation files (the "Software"), to deal
* used by FIRST Robotics Competition Team 948 - Newport Robotics Group (NRG948).
*
* <p>
* To initialize the libray, you must call the {@link Common#init(String...)}
* To initialize the library, you must call the {@link Common#init(String...)}
* method passing the name of the robot package. In the Command-based Robot,
* this must be done in the <code>Robot.initRobot()</code> method before the
* <code>RobotContainer</code> is created.<br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ of this software and associated documentation files (the "Software"), to deal
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.javatuples.Pair;

import com.nrg948.annotations.Annotations;

import edu.wpi.first.networktables.BooleanTopic;
Expand All @@ -44,8 +48,11 @@ of this software and associated documentation files (the "Software"), to deal
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringTopic;
import edu.wpi.first.wpilibj.Preferences;
import edu.wpi.first.wpilibj.shuffleboard.BuiltInLayouts;
import edu.wpi.first.wpilibj.shuffleboard.BuiltInWidgets;
import edu.wpi.first.wpilibj.shuffleboard.ComplexWidget;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardComponent;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardLayout;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardTab;
import edu.wpi.first.wpilibj.shuffleboard.SimpleWidget;
Expand Down Expand Up @@ -463,14 +470,28 @@ public <E extends Enum<E>> void visit(EnumValue<E> value) {
private static class ShuffleboardWidgetBuilder implements IValueVisitor {

private ShuffleboardLayout layout;
private RobotPreferencesValue metadata;

public ShuffleboardWidgetBuilder(ShuffleboardLayout layout) {
/**
* Constructs a visitor that creates Shuffleboard widgets for preference
* values.
*
* @param layout The Shuffleboard layout to add a widget for the visited
* value.
* @param metadata A {@link RobotPreferencesValue} annotation containing the
* value's metadata.
*/
public ShuffleboardWidgetBuilder(ShuffleboardLayout layout, RobotPreferencesValue metadata) {
this.layout = layout;
this.metadata = metadata;
}

@Override
public void visit(StringValue value) {
SimpleWidget widget = layout.add(value.getName(), value.getValue()).withWidget(BuiltInWidgets.kTextView);

configureWidget(widget);

GenericEntry entry = widget.getEntry();

entry.setString(value.getValue());
Expand All @@ -488,6 +509,9 @@ public void visit(StringValue value) {
public void visit(BooleanValue value) {
SimpleWidget widget = layout.add(value.getName(), value.getValue())
.withWidget(BuiltInWidgets.kToggleSwitch);

configureWidget(widget);

GenericEntry entry = widget.getEntry();

entry.setBoolean(value.getValue());
Expand All @@ -504,6 +528,9 @@ public void visit(BooleanValue value) {
@Override
public void visit(DoubleValue value) {
SimpleWidget widget = layout.add(value.getName(), value.getValue()).withWidget(BuiltInWidgets.kTextView);

configureWidget(widget);

GenericEntry entry = widget.getEntry();

entry.setDouble(value.getValue());
Expand All @@ -527,7 +554,9 @@ public <E extends Enum<E>> void visit(EnumValue<E> value) {
.forEach(e -> chooser.addOption(e.toString(), e));
chooser.setDefaultOption(currentValue.toString(), currentValue);

layout.add(value.getName(), chooser);
ComplexWidget widget = layout.add(value.getName(), chooser);

configureWidget(widget);

NetworkTableInstance ntInstance = NetworkTableInstance.getDefault();
NetworkTableEntry chooserEntry = ntInstance
Expand All @@ -543,6 +572,28 @@ public <E extends Enum<E>> void visit(EnumValue<E> value) {
(event) -> value.setValue(event.valueData.value.getString()));
}

/**
* Configures the visited value's widget according to the metadata information.
*
* @param widget The Shuffleboard widget to configure.
*/
private void configureWidget(ShuffleboardComponent<?> widget) {
if (layout.getType().equals(BuiltInLayouts.kGrid.getLayoutName())) {
int column = metadata.column();
int row = metadata.row();

if (column >= 0 && row >= 0) {
widget.withPosition(column, row);
}

int width = metadata.width();
int height = metadata.height();

if (width > 0 && height > 0) {
widget.withSize(width, height);
}
}
}
}

/** The name of the Shuffleboard tab containing the preferences widgets. */
Expand Down Expand Up @@ -581,16 +632,34 @@ public static void addShuffleBoardTab() {
.asClass());

classes.stream().map(c -> c.getAnnotation(RobotPreferencesLayout.class)).forEach(layout -> {
prefsTab.getLayout(layout.groupName(), layout.type())
var shuffleboardLayout = prefsTab.getLayout(layout.groupName(), layout.type())
.withPosition(layout.column(), layout.row())
.withSize(layout.width(), layout.height());
});

getAllValues().collect(Collectors.groupingBy(Value::getGroup)).forEach((group, values) -> {
ShuffleboardLayout layout = prefsTab.getLayout(group);
ShuffleboardWidgetBuilder builder = new ShuffleboardWidgetBuilder(layout);
values.stream().forEach((value) -> value.accept(builder));
if (layout.type().equals(BuiltInLayouts.kGrid.getLayoutName())) {
int gridColumns = layout.gridColumns();
int gridRows = layout.gridRows();

if (gridColumns > 0 && gridRows > 0) {
shuffleboardLayout
.withProperties(Map.of("Number of columns", gridColumns, "Number of rows", gridRows));
}
}
});

getFields()
.map(RobotPreferences::mapToPair)
.collect(Collectors.groupingBy(p -> p.getValue1().group))
.forEach((group, pairs) -> {
ShuffleboardLayout layout = prefsTab.getLayout(group);

pairs.stream()
.forEach(pair -> {
ShuffleboardWidgetBuilder builder = new ShuffleboardWidgetBuilder(layout, pair.getValue0());

pair.getValue1().accept(builder);
});
});
}

/** Returns a stream of fields containing preferences values. */
Expand All @@ -616,6 +685,11 @@ private static Value mapToValue(Field field) {
return null;
}

/** Maps a preferences field to a pair of its annotation and value instance */
private static Pair<RobotPreferencesValue, Value> mapToPair(Field field) {
return Pair.with(field.getAnnotation(RobotPreferencesValue.class), mapToValue(field));
}

/** Returns all preferences values. */
private static Stream<Value> getAllValues() {
return getFields().map(RobotPreferences::mapToValue).filter(v -> v != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,20 @@ of this software and associated documentation files (the "Software"), to deal
* @return The layout type.
*/
String type() default "List Layout";

/**
* The number of columns in a grid layout. This value is ignored if the
* {@link RobotPreferencesLayout#type} is "List Layout".
*
* @return The number of columns.
*/
int gridColumns() default -1;

/**
* The number of rows in a grid layout. This value is ignored if the
* {@link RobotPreferencesLayout#type} is "List Layout".
*
* @return The number of rows.
*/
int gridRows() default -1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,35 @@ of this software and associated documentation files (the "Software"), to deal
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RobotPreferencesValue {
/**
* The column position in a grid layout. This value is ignored if the
* {@link RobotPreferencesLayout#type} of the value's group is "List Layout".
*
* @return The column position of this value.
*/
int column() default -1;

/**
* The row position in a grid layout. This value is ignored if the
* {@link RobotPreferencesLayout#type} of the value's group is "List Layout".
*
* @return The row position of this value.
*/
int row() default -1;

/**
* The width of this value in a grid layout. This value is ignored if the
* {@link RobotPreferencesLayout#type} of the value's group is "List Layout".
*
* @return The width of this value.
*/
int width() default -1;

/**
* The height of this value in a grid layout. This value is ignored if the
* {@link RobotPreferencesLayout#type} of the value's group is "List Layout".
*
* @return The height of this value.
*/
int height() default -1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ of this software and associated documentation files (the "Software"), to deal
* group of PID preferences to be added automatically to Shuffleboard.
*
* <p>
* The followng example defines the PID constants in a class derived from
* The following example defines the PID constants in a class derived from
* {@link PIDSubsystem} and enables a layout to be generated for the
* Shuffleboard.<br>
*
Expand Down

0 comments on commit 1aad77f

Please sign in to comment.