forked from wpilibsuite/shuffleboard
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add controls and telemetry to camera stream widgets (wpilibsuite#425)
* Add API for saving properties in widgets without having to export them * Add framerate, compression, and resolution controls to camera sources Bump plugin version to 1.1.0 Bundle empty image in resources dir * Debounce URL updates, fix base URLs not being correct * Fix widgets not re-enabling if initially loaded with a disconnected source * Fix widgets being disabled at the wrong time * Use /stream.mjpg to avoid 404s * Reduce fragility of loading broken or incompatible save files * Load sources last Widgets can have properties that manipulate their sources, so loading the sources after their properties are set up lets properties be set when loading a saved widget * Make "active" and "connected" properties atomic * Limit stream resolution to 1920x1080 Adds range limiting options to number fields so this can be enforced both through the widget and through the source itself
- Loading branch information
1 parent
553887d
commit cc4b583
Showing
27 changed files
with
1,211 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
api/src/main/java/edu/wpi/first/shuffleboard/api/properties/SaveProperties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package edu.wpi.first.shuffleboard.api.properties; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Do not use this annotation directly - use {@link SavePropertyFrom} instead. This annotation only exists so that | ||
* {@code SavePropertyFrom} can be repeated. | ||
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.FIELD) | ||
public @interface SaveProperties { | ||
|
||
/** | ||
* Do not use. | ||
*/ | ||
SavePropertyFrom[] value(); | ||
|
||
} |
91 changes: 91 additions & 0 deletions
91
api/src/main/java/edu/wpi/first/shuffleboard/api/properties/SavePropertyFrom.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package edu.wpi.first.shuffleboard.api.properties; | ||
|
||
import edu.wpi.first.shuffleboard.api.widget.Widget; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Repeatable; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Flags a property of a field in a widget class to be saved as part of that widget. | ||
* | ||
* <p>For example, take a widget class has a slider that controls the speed of something. In the old API, the widget | ||
* would have export its properties directly (which doesn't give descriptive names in the widget's context, and can | ||
* run into conflicts), or create new properties with the desired names and bind those to the properties of the slider. | ||
* This takes a lot of code and makes the class less readable; there is a lot of boilerplate. It is also not ideal, | ||
* because properties exported via {@link Widget#getProperties()} are also configurable through the property editor for | ||
* the widget, even if the author does not want them to be user-editable, or if they are already editable through the | ||
* controls in the widget; for example, the value of the slider changes when a user moves the slider - it does not need | ||
* to be editable another way. | ||
* | ||
* <pre>{@code | ||
* // Bad API -- exposes properties to the user that should not be editable! | ||
* class MyWidget implements Widget { | ||
* | ||
* private final Slider speedSlider = new Slider(); | ||
* | ||
* private final DoubleProperty speed = new SimpleDoubleProperty(this, "speed"); | ||
* private final DoubleProperty minSpeed = new SimpleDoubleProperty(this, "minSpeed"); | ||
* private final DoubleProperty maxSpeed = new SimpleDoubleProperty(this, "maxSpeed"); | ||
* | ||
* public MyWidget() { | ||
* speedSlider.valueProperty().bindBidirectional(speed); | ||
* speedSlider.minProperty().bindBidirectional(minSpeed); | ||
* speedSlider.maxProperty().bindBidirectional(maxSpeed); | ||
* } | ||
* | ||
* @literal @Override | ||
* public List<Property> getProperties() { | ||
* return ImmutableList.of( | ||
* speed, | ||
* minSpeed, | ||
* maxSpeed | ||
* ); | ||
* } | ||
* } | ||
* }</pre> | ||
* | ||
* <p>A better API is to use this annotation to specify the properties to save and the names to save them as. No | ||
* properties have to be exposed to users, no dummy properties have to be created to set the name, and the code is | ||
* <i>much</i> clearer: | ||
* | ||
* <pre>{@code | ||
* class MyWidget implements Widget { | ||
* | ||
* @literal @SavePropertyFrom(propertyName = "value", savedName = "speed") | ||
* @literal @SavePropertyFrom(propertyName = "min", savedName = "minSpeed") | ||
* @literal @SavePropertyFrom(propertyName = "max", savedName = "maxSpeed") | ||
* private final Slider speedSlider = new Slider(); | ||
* | ||
* } | ||
* }</pre> | ||
* | ||
* <h2>Using</h2> | ||
* An empty string for either {@code propertyName} or {@code savedName} will throw an exception when the widget is | ||
* attempted to be saved or loaded: {@code @SavePropertyFrom(propertyName="", savedName="")} is not allowed. | ||
* <br> | ||
* The property must have public "getter" and "setter" methods; for example, a property with name "foo" must have | ||
* {@code public Foo getFoo()} and {@code public void setFoo(Foo newFoo)} methods (boolean properties may also use | ||
* the prefix {@code is} instead of {@code get}). | ||
* <br> | ||
* Note that this only uses getter and setter methods, so this annotation is fully compatible with standard Java beans. | ||
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.FIELD) | ||
@Repeatable(SaveProperties.class) | ||
public @interface SavePropertyFrom { | ||
|
||
/** | ||
* The name of the property to save. | ||
*/ | ||
String propertyName(); | ||
|
||
/** | ||
* The name to save the property as. By default, the name of the property is used, but can be overridden by setting | ||
* this value. | ||
*/ | ||
String savedName() default ""; | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
api/src/main/java/edu/wpi/first/shuffleboard/api/properties/SaveThisProperty.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package edu.wpi.first.shuffleboard.api.properties; | ||
|
||
import edu.wpi.first.shuffleboard.api.widget.Widget; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Marks a JavaFX property field in a component to be saved. Properties that are made available via | ||
* {@link Widget#getProperties()} will be saved and loaded without needing this annotation; this annotation should only | ||
* be placed on properties that widget authors do not want to be made user-configurable through the properties editor, | ||
* or when the name of the property is not particularly descriptive. | ||
* | ||
* <p>For example, this will save a property named {@code foo} with the name of the property ("foo"): | ||
* <pre>{@code | ||
*@literal @SaveThisProperty | ||
* private final Property<Foo> foo = new SimpleObjectProperty(this, "foo", new Foo()); | ||
* }</pre> | ||
* This will save it as "a foo": | ||
* <pre>{@code | ||
*@literal @SaveThisProperty(name = "a foo") | ||
* private final Property<Foo> foo = new SimpleObjectProperty(this, "foo", new Foo()); | ||
* }</pre> | ||
* | ||
* <p>If the property has no name (i.e. the name string is {@code null} or {@code ""}), then the annotation <i>must</i> | ||
* set the name. Otherwise, an exception will be thrown when attempting to save or load the widget. | ||
* <pre>{@code | ||
* // No name set! | ||
*@literal @SaveThisProperty | ||
* private final Property<Foo> foo = new SimpleObjectProperty(this, "", new Foo()); | ||
* }</pre> | ||
* <pre>{@code | ||
* // Good - the name is set in the annotation | ||
*@literal @SaveThisProperty(name = "a foo") | ||
* private final Property<Foo> foo = new SimpleObjectProperty(this, "", new Foo()); | ||
* }</pre> | ||
* | ||
* <p>Placing this annotation on a field that does not subclass {@link javafx.beans.property.Property} will have no | ||
* effect. | ||
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.FIELD) | ||
public @interface SaveThisProperty { | ||
|
||
/** | ||
* The name to save the property as. The only constraint on the name is that the characters should be ASCII codes | ||
* for maximum compatibility. | ||
*/ | ||
String name() default ""; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
api/src/main/java/edu/wpi/first/shuffleboard/api/util/ReflectionUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package edu.wpi.first.shuffleboard.api.util; | ||
|
||
import java.lang.reflect.Field; | ||
|
||
/** | ||
* Utility class for working with reflection. | ||
*/ | ||
public final class ReflectionUtils { | ||
|
||
private ReflectionUtils() { | ||
throw new UnsupportedOperationException("This is a utility class!"); | ||
} | ||
|
||
/** | ||
* Gets the value of a field. | ||
* | ||
* @param instance the instance of the class to get the field's value from | ||
* @param field the field to get the value of | ||
* @param <T> the type of object in the field | ||
* | ||
* @return the value of the field | ||
* | ||
* @throws ReflectiveOperationException if the value of the field could not be retrieved | ||
*/ | ||
public static <T> T get(Object instance, Field field) throws ReflectiveOperationException { | ||
field.setAccessible(true); | ||
return (T) field.get(instance); | ||
} | ||
|
||
/** | ||
* Gets the value of a field. Reflective exceptions are wrapped and re-thrown as a runtime exception. | ||
* | ||
* @param instance the instance of the class to get the field's value from | ||
* @param field the field to get the value of | ||
* @param <T> the type of object in the field | ||
* | ||
* @return the value of the field | ||
* | ||
* @throws RuntimeException if a reflective exception was thrown while attempting to get the value of the field | ||
*/ | ||
public static <T> T getUnchecked(Object instance, Field field) { | ||
try { | ||
return get(instance, field); | ||
} catch (ReflectiveOperationException e) { | ||
throw new RuntimeException("Could not read field: " + field.toGenericString(), e); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.